On Wed, 8 Dec 2004 03:13:07 +0900, Nicholas Van Weerdenburg
<vanweerd / gmail.com> wrote:
> On Wed, 8 Dec 2004 01:06:53 +0900, Austin Ziegler
> <halostatue / gmail.com> wrote:
[snip]
>> The "need" for immutability is very, erm, mutable and can be
>> worked around with various design decisions.
> I agree- good interface design effectively creates immutable
> classes if desired. But that can be an issue because the object
> may only desire immutability later in its lifecycle, or too
> certain clients.

I'm still not convinced, and I think that the problem that I'm
seeing is that you're assuming that an object's desire for
immutability is a necessary thing. I don't see such. What I'm trying
to draw out from you is a discussion of the design where you feel
that you need this feature to help me understand why this
fundamental change to the language would be a good one (and, by
extension, help others understand that as well). Alternatively, in
such a discussion, we actually figure out what you want to do with
Ruby and how not to modify Ruby so fundamentally by making your
design more "Ruby"-like.

I'm not claiming to be "all that" as a Ruby developer, but I think
that I've managed to get a fair idea of the feel of good Ruby design
over the last two and a half years :)

> Also, good design is an ideal case. In a large project, things can
> become ugly and to be able to enforce a high-degree of control via
> immutable objects can be beneficial, IMHO. Good design requires
> good contracts to enforce it.

Again, I don't necessarily agree. I think that's an absolute
requirement in Java, but I think that Ruby doesn't really require
that.

> There are three things that make up an immutable object so far in
> our discussion:
> 1. frozen objects -this could also be done with good interface
>    design for certain cases. other alternatives to freeze would be
>    useful to know- wrap/delegate, undeffing, etc.
> 2. frozen attributes (assignmentFreeze) -mostly appplies to
>    inheritence, so private instance variables would help here.
> 3. frozen value objects (referred to objects of a certain type)
>    -not really discussed yet, but seems important as without it,
>    freeze is only slushy.

I and others pointed out a fundamental problem with #3 and Ruby --
how do you define type? If you're thinking in terms of Java types,
that's definitely the wrong way to think about type in Ruby. An
object's class is not necessarily it's type in Ruby. (See my
discussion at the end of this email about StrictVar.)

> It's hard to describe the benefits of immutables in a short
> example, but I think it's similar in nature to global variables-
> with mutables, edits can be done in many places, leading to bugs
> as well as maintenance issues.

Again, I'm not sure that this is a guaranteed situation in Ruby.
Look at Rails -- trying to implement an immutable in a Rails
application would probably break the application. I developed a bug
tracking application in Perl (and will be reimplementing it in Ruby
at some point) and have never had a need for an immutable. In the
C++ and PL/SQL billing and CRM application I worked on a few years
back, I don't recall any immutable objects, or any problems because
we didn't have them.

[snip]

> If an object should protect it's invariant nature, then it would
> be nice for ruby to provide some capability for immutable value
> objects- maybe something like:

>   # returns defensive copies
> value_reader :name, :location, :value

Not hard:

  class << Module
    def value_reader(*symbols)
      symbols.each do |sym|
        self.define_method(sym) do ||
          self.instance_variable_get("@{#sym}").dup
        end
      end
    end
  end

Untested air code :) You could implement it with
Marshal.load(Marshal.dump(variable-value)) for a deep clone.

I'm not sure that the distinction between value objects and
reference objects is needed -- the distinction isn't present in
Ruby. (Well, it is, but for the most part, it's transparent to you,
the programmer.)

>> You assert the value of immutability, but you haven't actually
>> demonstrated the value. I'm really not trying to be difficult
>> here, but what sort of immutability to you mean in Java -- and
>> why do you then need it in Ruby? I find that most of the time
>> when people say that they want particular features in Ruby they
>> do so because they're not *thinking* in Ruby, but in other
>> languages that they have to deal with on a daily basis to pay the
>> bills. I know I do it, from time to time, with C++ now that I'm
>> making my money from that.
> I'm still learning to "think" in Ruby for sure, but the essence of
> the need comes from non-trivial systems without a perfect design.

Again, I'm not really sure that this is true for Ruby. I think that
the essence of the need comes from heavyweight Java and EJB designs,
not dynamic languages. As Morpheus said in _The Matrix_, "Free your
mind."

> Removing singletons and making certain domain objects immutable is
> somewhat like putting aspects of the "Law of Demeter" into place.
> http://c2.com/cgi/wiki?LawOfDemeter

I'm willing to engage in a longer discussion on the design you're
seeing for this -- because I think that there are probably other
ways to approach this in a language like Ruby without requiring
changes to the core language that would, IMO, be detrimental.

> Another way of thinking about is in terms of object lifecycle- if
> it becomes immutable at some point, there is less to worry about.
> In a large system, I would freeze and object just prior to
> releasing into the "wild"- e.g. outside the boundaries of my
> subsystem.

Why? All I have to do to unfreeze your object is dup it. Why limit
what people who may write on top of your classes can do? Certainly,
don't necessarily trust what they provide back to you without
verification, but I don't see any reason to actually do this.

>>> And is there anyway to may attributes private so that subclasses
>>> can't see them? IIRC, this is possible feature of Rite. This
>>> would remove some of the need, since immutability is enforced by
>>> invisibility.
>> Um. What do you mean by "attributes"? If you mean that which is
>> generated by attr_accessor, then:
>> 
>> class A
>>   attr_accessor :foo
>>   private :foo, :foo=
>> end # => A
>> class B < A; end # => nil
>> B.new.foo = :bar
>>  # => NoMethodError: private method `foo=' called for
>>  # #<B:0x2b52c38> 
> I meant the case where the instance variable @foo is accessed in
> side the class B.

Right, but Matz has suggested that B should never access @foo
directly, but should try to do so through the foo method. I don't
see a reason to make @foo itself "private" in the sense that Java
and C++ have private variables.

>> If you mean instance variables, no, and that wouldn't work anyway
>> -- it would simply be a source of errors.
>> class A
>>   attr_accessor  :foo
>>   private_var    :@foo
>> end
> private_var is a concept?

Yes.

>> I don't know what itsme really wants, because I think that the
>> idea of making variable bindings -- instance or otherwise --
>> frozen is a bad idea. Freezing an object freezes that object's
>> state -- which includes assignment to instance variables,
>> certainly, but also inclues adding singleton methods, modifying
>> the singleton object, extending the object, etc.
> I think private variables would alleviate much of the need.

I don't think so, actually. I would like to see, as I said, a @<foo>
form (it may be different) so that you have privately decorated
instance variables generated by the Ruby parser so that there's no
collision from modules to classes, but ... that's a minor desire.

In earlier messages, you mentioned a "type freeze" for a variable.
There's no reason you can't do that indirectly:

  class StrictVar
    def initialize(value, &block)
      @value = value
      @cond  = block
    end

    def cond=(cond)
      if @cond
        raise ArgumentError
      else
        @cond = cond
      end
    end

    attr_accessor :value
    def value=(val)
      if @cond and @cond.cal(val)
        @value = val
      else
        raise TypeError
      end
    end
  end

That, of course, isn't a complete implementation of the delegate
that you'd want to do for that, but you could do an arbitrary block:

  a = StrictVar.new("a") { |x| x.kind_of?(String) }

Now, the object referred to by StrictVar will only be able to be
Strings.

-austin
-- 
Austin Ziegler * halostatue / gmail.com
               * Alternate: austin / halostatue.ca