"David A. Black" <dblack / wobblini.net> wrote
> I think where I came in was discussing the freezing of local variable
> bindings, which I didn't (and don't) like because it's such a
> different kind of freezing from freezing a specific object (as done by
> Kernel#freeze).  Which you disagreed with, etc. :-)

Leaving aside freezing local variables, I am curious what you think of
freezing controlled at the level of (the bindings of) individual Ruby
instance variables, rather than strictly for entire objects. So I can say
things like:

class Home; def freeze ... end; end
myHome.freeze  # cannot change door or door-knob, but can re-paint door
myHome.door = otherDoor #=> error
myHome.door.knob = otherKnob #=> error
myHome.door.color = otherColor #=> OK

Today's Object#freeze can be implemented using this, I believe (extended to
deal with special cases for arrays, hashes, etc, and with the fact that
"freeze all instance variables, ... " in Ruby might mean prohibit dynamic
addition of new ivars). The converse is not true i.e. instance-variable
level freeze cannot be implemented using today's Object#freeze, and the
myHome example above is impossible to implement.

Further, since a class can be re-opened and extended, including introducing
new instance variables, should a custom #freeze really be forced to have a
scope across all possible extensions?

> I certainly don't see any reason not to define custom freeze's for
> specific objects, though there might be certain things you'd want to
> watch out for.  One problem with the "deep freeze" might be that while
> objects cannot share instance variables, they can share state by
> having i.vars that refer to the same object.  So if it's in the
> interest of one to freeze that deep object (as opposed to just
> freezing the binding), but not in the interest of another, I guess
> weird things could happen.

True. Note, though, that this is not specific to freeze. In general, such
aliasing of objects (anytime the object graph is not a tree, which is most
of the time) is an issue for all mutative methods, including freeze.

> I'm thinking of something like (starting with non-weird case):
>
>    class A
>      attr_accessor :x
>      def x_add(str)
>        @x << str
>      end
>    end
>
>    class B
>      attr_accessor :y
>    end
>
>    a = A.new
>    b = B.new
>
>    shared = "hi"
>
>    a.x = shared
>    b.y = shared
>
>    b.freeze            # freezes @y binding, but doesn't freeze @y
>
>    a.x_add(" there")   # so this works
>
>    puts b.y            # hi there
>
> Now if you deep-froze b in such a way that @y (the object itself) was
> frozen, then a would be in for a surprise when it called x_add.
> (That's the "weird" case :-)  It's probably a fairly marginal
> scenario, but it's definitely not impossible.

Absolutely correct. But either a and b are meant to work with shared state
(in which case a.x_add, b.y, and freeze have no surprises), or they are not
(in which case b.y and a.x_add themselves are anomolies, even without
#freeze).

This is the reason why there are design approaches and even language
features meant to support encapsulating such aliasing within manageable
boundaries while still permitting arbitrary object graph structures within
them. Such things might not be in Ruby, but that does not make them less
worth considering.

Cheers.