A
Austin Ziegler
On Wed, 8 Dec 2004 01:06:53 +0900, Austin Ziegler
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.)
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.
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.
private_var is a concept?
Yes.
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