DbC, const and freeze

Hmm. I tend to do Test Driven Development and Design by Contract.

I find the two approaches mesh very very well indeed.

Now in C++ I also use "const" very heavily.

It works very very well. It not only enforces certain design decisions, more importantly it documents them.

If you hunting a bug that is mystikally changing a data value, a well "const"'d C++ program allows you to ignore so much code and rapidly zoom in on the real problem.

I was just thinking about the Object freeze method.

Now that is quite a powerful assert really.

It asserts that this object needs not and must not ever change.

A bit more powerful than "const" in away.

In fact too powerful.

What I really want is something like the C++

void myFunc( const obj& foo)
{
    // I know foo won't get mangled by this routine...
}

or in Ruby...

def my_func( foo)
   foo.freeze

ensure
   foo.unfreeze
end

Accept there is no unfreeze.

This would work, but is a bit expensive...

def my_func( foo_p)
   foo = foo_p.clone.freeze

end

It also misses the notion of "const" methods.

In C++ you are only allowed to invoke const methods of a const object.

For example, look at this hole...
irb
irb(main):001:0> a = [[1,2],[3,4]]
=> [[1, 2], [3, 4]]
irb(main):002:0> a.freeze
=> [[1, 2], [3, 4]]
irb(main):003:0> a << [9]
RuntimeError: can't modify frozen array
   from (irb):3:in `<<'
   from (irb):3
irb(main):004:0> a[0]<<1
=> [1, 2, 1]
irb(main):005:0> a
=> [[1, 2, 1], [3, 4]]
irb(main):006:0>

Even though "a" was frozen, I still could modify the things "a" contained.

Although Ruby has a C++'ish distinct between public and private methods, it doesn't allow you to use that with "const" to identify which things this class owns and freeze when this object is frozen.

Hmm. I suppose I could override freeze and freeze my instance variables and then myself. That would do.

Any other ideas on getting the effect of C++ "const" in Ruby?

John Carter Phone : (64)(3) 358 6639
Tait Electronics Fax : (64)(3) 359 4632
PO Box 1645 Christchurch Email : john.carter@tait.co.nz
New Zealand

Carter's Clarification of Murphy's Law.

"Things only ever go right so that they may go more spectacularly wrong later."

From this principle, all of life and physics may be deduced.

···

from :0

Fair enough. How about this:

irb(main):001:0> class Object
irb(main):002:1> def deep_freeze
irb(main):003:2> self.freeze
irb(main):004:2> if self.respond_to? :each
irb(main):005:3> self.each do |i|
irb(main):006:4* i.deep_freeze
irb(main):007:4> end
irb(main):008:3> end
irb(main):009:2> end
irb(main):010:1> end
=> nil
irb(main):011:0> a=[[[1,2,3],[4,5]],[6,7]]
=> [[[1, 2, 3], [4, 5]], [6, 7]]
irb(main):012:0> a.deep_freeze
=> [[[1, 2, 3], [4, 5]], [6, 7]]
irb(main):013:0> a << 9
TypeError: can't modify frozen array
        from (irb):13:in `<<'
        from (irb):13
irb(main):014:0> a[0] << 9
TypeError: can't modify frozen array
        from (irb):14:in `<<'
        from (irb):14
irb(main):015:0> a[0][0] << 9
TypeError: can't modify frozen array
        from (irb):15:in `<<'
        from (irb):15
irb(main):016:0>

Does anyone else love that name? :wink:

Ryan

···

On 10/19/05, John Carter <john.carter@tait.co.nz> wrote:

For example, look at this hole...
irb
irb(main):001:0> a = [[1,2],[3,4]]
=> [[1, 2], [3, 4]]
irb(main):002:0> a.freeze
=> [[1, 2], [3, 4]]
irb(main):003:0> a << [9]
RuntimeError: can't modify frozen array
        from (irb):3:in `<<'
        from (irb):3
        from :0
irb(main):004:0> a[0]<<1
=> [1, 2, 1]
irb(main):005:0> a
=> [[1, 2, 1], [3, 4]]
irb(main):006:0>

Even though "a" was frozen, I still could modify the things "a" contained.

Btw: this posting of Ryan was truncated on the news side. Does anybody
else have it truncated at their news server, too?

>
> For example, look at this hole...
> irb
> irb(main):001:0> a = [[1,2],[3,4]]
> => [[1, 2], [3, 4]]
> irb(main):002:0> a.freeze
> => [[1, 2], [3, 4]]
> irb(main):003:0> a << [9]
> RuntimeError: can't modify frozen array
> from (irb):3:in `<<'
> from (irb):3
> from :0
> irb(main):004:0> a[0]<<1
> => [1, 2, 1]
> irb(main):005:0> a
> => [[1, 2, 1], [3, 4]]
> irb(main):006:0>
>
> Even though "a" was frozen, I still could modify the things "a" contained.

Fair enough. How about this:

<snip/>

Apart from the fact that deep_unfreeze is missing...

I still think that the concept of constness doesn't fit Ruby well.
There are too many places where you can fiddle with state where you
normally (i.e. in other PL's) aren't allowed to. Adding #unfreeze
wouldn't help either because it would break the contract of freeze
that is used today - IOW code may break.

In some cases where modification of an argument must be prevented a
delegation based approach may work.

I wouldn't go as far as to state that const is overrated but the fact
that C++ needed to introduce keyword "mutable" shows that it's not
about simply freezing state of an instance. You rather want to freeze
observed state... Which brings up an idea....

module Kernel
private
  def const(obj)
    meth = {}
    sc = class<<obj;self;end
    obj.methods.grep(/=$/).each do |m|
      meth[m]=obj.method m
      sc.send(:define_method, m) { raise "ConstError" }
    end

    begin
      yield obj
    ensure
      meth.each do |m,mm|
        sc.send(:define_method, m, &mm)
      end
    end
  end
end

Test = Struct.new(:name, :val)

=> Test

obj = Test.new "foo", "bar"

=> #<struct Test name="foo", val="bar">

obj.name = "hello"

=> "hello"

const obj do

?> obj.name = "new name"

end

RuntimeError: ConstError
        from (irb):8:in `name='
        from (irb):24
        from (irb):12:in `const'
        from (irb):23

p obj.name

"hello"
=> nil

obj.name="new 2"

=> "new 2"

p obj.name

"new 2"
=> nil

This can easily be extended to multiple instances... Maybe
modification of instance_variable_set does the job, too.

Does anyone else love that name? :wink:

Can you do #deep_think, too? We then could have #deep_thought?... :slight_smile:

Kind regards

robert

···

2005/10/20, Ryan Leavengood <leavengood@gmail.com>:

On 10/19/05, John Carter <john.carter@tait.co.nz> wrote:

        from :0

Does not freeze instance variables.

instance_variables.each do |ivar|
   instance_variable_get(ivar).deep_freeze
end

I think.

···

On Oct 20, 2005, at 7:01 AM, Ryan Leavengood wrote:

On 10/19/05, John Carter <john.carter@tait.co.nz> wrote:

For example, look at this hole...
irb
irb(main):001:0> a = [[1,2],[3,4]]
=> [[1, 2], [3, 4]]
irb(main):002:0> a.freeze
=> [[1, 2], [3, 4]]
irb(main):003:0> a << [9]
RuntimeError: can't modify frozen array
        from (irb):3:in `<<'
        from (irb):3
        from :0
irb(main):004:0> a[0]<<1
=> [1, 2, 1]
irb(main):005:0> a
=> [[1, 2, 1], [3, 4]]
irb(main):006:0>

Even though "a" was frozen, I still could modify the things "a" contained.

Fair enough. How about this:

class Object
  def deep_freeze
    self.freeze
    if self.respond_to? :each
      self.each do |i|
        i.deep_freeze
      end
    end
  end
end

--
Eric Hodel - drbrain@segment7.net - http://segment7.net
FEC2 57F1 D465 EB15 5D6E 7C11 332A 551C 796C 9F04

For example, look at this hole...
irb
irb(main):001:0> a = [[1,2],[3,4]]
=> [[1, 2], [3, 4]]
irb(main):002:0> a.freeze
=> [[1, 2], [3, 4]]
irb(main):003:0> a << [9]
RuntimeError: can't modify frozen array
        from (irb):3:in `<<'
        from (irb):3
        from :0
irb(main):004:0> a[0]<<1
=> [1, 2, 1]
irb(main):005:0> a
=> [[1, 2, 1], [3, 4]]
irb(main):006:0>

Even though "a" was frozen, I still could modify the things "a" contained.

Fair enough. How about this:

<snip/>

Apart from the fact that deep_unfreeze is missing...

No, its not (well, you don't get the original back).

Marshal.load(Marshal.dump(obj))

I still think that the concept of constness doesn't fit Ruby well.
There are too many places where you can fiddle with state where you
normally (i.e. in other PL's) aren't allowed to. Adding #unfreeze
wouldn't help either because it would break the contract of freeze
that is used today - IOW code may break.

Of course, Marshal.load(Marshal.dump(obj)) works for regular unfreeze as well.

···

On Oct 20, 2005, at 7:40 AM, Robert Klemme wrote:

2005/10/20, Ryan Leavengood <leavengood@gmail.com>:

On 10/19/05, John Carter <john.carter@tait.co.nz> wrote:

--
Eric Hodel - drbrain@segment7.net - http://segment7.net
FEC2 57F1 D465 EB15 5D6E 7C11 332A 551C 796C 9F04

I meant, it's missing from the sample implementation. But: Marshal does something completely different: creating a copy is something different from temporary freezing an instance's (or instance graph's) state. It starts with different identities and doesn't stop at performance... You *can* use that in certain scenarios but I doubt that it's a full replacement for constness.

Kind regards

    robert

···

Eric Hodel <drbrain@segment7.net> wrote:

On Oct 20, 2005, at 7:40 AM, Robert Klemme wrote:

2005/10/20, Ryan Leavengood <leavengood@gmail.com>:

On 10/19/05, John Carter <john.carter@tait.co.nz> wrote:

For example, look at this hole...
irb
irb(main):001:0> a = [[1,2],[3,4]]
=> [[1, 2], [3, 4]]
irb(main):002:0> a.freeze
=> [[1, 2], [3, 4]]
irb(main):003:0> a << [9]
RuntimeError: can't modify frozen array
        from (irb):3:in `<<'
        from (irb):3
        from :0
irb(main):004:0> a[0]<<1
=> [1, 2, 1]
irb(main):005:0> a
=> [[1, 2, 1], [3, 4]]
irb(main):006:0>

Even though "a" was frozen, I still could modify the things "a"
contained.

Fair enough. How about this:

<snip/>

Apart from the fact that deep_unfreeze is missing...

No, its not (well, you don't get the original back).

Marshal.load(Marshal.dump(obj))