On 10/19/07, ara.t.howard [email protected] wrote:
objects!).
The main reason, I’ve never been completely comfortable with the Ruby
conflation of singleton class and metaclass. Or maybe it’s the
conflation of singleton methods and class methods.
Seems to me that singleton methods SHOULD only be applicable to a
single object. A singleton class of a non-class is constrained to
containing only singleton methods in that sense, because it can’t be
subclassed. A singleton class of a class is not. It’s actually the
class that’s the singleton here in that it’s the only allowed instance
of its, dare I say it, metaclass.
But I digress.
it’s the concept of inheritance that breaks
encapsulation, this is of course generally accepted by oop geeks, and
ruby doesn’t change this wart on oop’s many graces.
Agreed.
Inheritance is a power tool, valuable and sometimes dangerous. Did I
mention that it’s valuable!
unfettered methods of their class because requiring the ‘self.class’
prefix also makes them public which, of course, seriously breaks
encapsulation.
I’m not sure that I totally agree here. There might well be class
methods which the class doesn’t/shouldn’t expose even to its
instances.
Perhaps there are use cases for a fourth visibility (in addition to
public, protected, and private) which makes a method visible to
instances, between public and protected.
in point of fact inheritance is designed to break encapsulation so
that we can reuse code and remain sane. child classes, mixins,
friend classes et al are all about tearing down the barriers between
bits of code so we don’t descend.into.dot.dot.dot.dot.hell and
develop carpal tunnel from all that code pasting
Yes.
to grasp this concept though.
I understand what you’re saying Ara, but it’s a little hard to grasp
because there really isn’t anything which reifies ‘a class hierarchy’.
In reality a class variable is associated with a class, which of
course is the root of a class hierarchy, but the way they are
implemented, it really means they get associated with a kind of
snapshot of the hierarchy as it existed at the time the class variable
was defined. I say this by way of explaining the problems of defining
a class variable in a superclass, AFTER a subclass has already defined
a class variable with the same name.
This is one instance of a small set of curiosities in Ruby due to
problems in re-building the runtime structures when certain
inheritance related changes occur. To me it seems to be similar to
the problems with the semantics of module re-inclusion.
Now I’m not sure that Matz was directly inspired by Smalltalk in
coming up with class variables, but given my background I tend to
think so. Ruby class variable have a lot in common with Smaltalk’s
certainly in terms of inheritance and visibility in both class and
instance methods. Assuming he was…
One of the biggest differences (and I’d say for the most part it’s an
improvement) between Ruby and Smalltalk is that in Smaltalk variables
are declared, whereas in Ruby they are defined in the process of
expression parsing/execution.
In Smalltalk, you declare a class with something like
Object subclass: #Foo
instanceVariableNames: ‘instVar’
classVariableNames: ‘ClassVar’
classInstanceVariableNames: ‘classInstVar’
poolDictionaries: ‘PoolDict’
And the compiler uses these declarations when compiling a method. In
Smalltalk instance variables and class instance variables can be bound
to a slot at a fixed offset from the begining of the instance, or
class. Class variables can be bound to the value slot in a dictionary
associated with the class. I’ll ignore pool dictionaries, they serve
some of the purposes of the name space aspect of modules.
The compiler searches up the hierarchy to find inherited class
variables, and the methods for manipulating the classVariableNames
list of a class validate that a conflict can’t be created. Changing
the class declaration in Smalltalk triggers a recompilation of all
methods of the class and its subclasses.
Ruby substitutes run-time binding binding of variables, rather than
being at a fixed offset, ruby instance (and class instance) variables
are bound to a value slot in a hash associated with the instance or
class, class variables are bound to a hash as well (the last time I
looked it was the same hash, which works because the key contains the
sigil, so the keys :@instance_var, and :@@class_var can both be in the
hash, reflection methods like Object#instance_variables filter their
results to hide this implementation. Note also that this description
is from my memory of reading the MRI 1.8.x code, other Ruby 1.8
implementations might well use different implementation techniques but
should have the same semantics.
So what was my point here? Oh yes. I was talking about what happens
when you make certain changes to the inheritance structure. If we
had:
class A
end
class B < A
@@cv = 42
end
And then later
class A
@@cv = 57
end
Ruby currently at most warns about a conflict then goes ahead and adds
a new class variable to A, leaving the existing one in C.
I think that the change in 1.9 was motivated by this anomaly, by doing
away with the inheritance of class variables we avoid the surprise.
On the other hand, as you point out below, the baby might be being
thrown out with the bath water!
It seems to me that another solution would be to simply remove the
class variable from a subclass when a superclass subsequently creates
one with the same name. Sure B’s view of @@cv would change from 42 to
57, but that’s consistent with the intended semantics, I think.
On the other hand, there might be subtleties I can’t yet fathom, much
like I still don’t understand why 1.9 changed the semantics of:
module M
def meth
“M”
end
end
class A
include M
end
class B < A
def meth
“B”
end
end
class C
include M
end
C.new.meth
So that in 1.9 this used to result in “M” but then they changed it
back to the 1.8 semantics which ignore the re-inclusion and results in
“B”.
The worst thing about them, in my view, is the amount of confusion
they have caused. The fact that there is something called a “class
variable” – and that it looks vaguely like an instance variable –
has been a huge obstacle for people trying to get a grasp on the idea
that classes are objects and can, via instance variable, have their
own state. Class variables throw a big shadow over that otherwise very
consistent and lucid state of things. I’ve seen this happen again and
again for seven years.
absolutely correct.
i think that the problem should be dissected, it’s really two problems:
- Ruby class variables look too much like instance variables
because of syntax @@ vs @
- The Ruby class variable implementation has a few rough edges.
The first problem makes me tend to agree that it would have been
better to choose another sigil for denoting class variables, although
this would seem to have a pretty sizable cost in backward
compatibility.
The second problem would seem to be fixable, at some cost to backward
compatibility, but 1.9 has already taken one path at fixing it.
In Ruby > 1.8, class variables are going to be somewhat more strictly
per-class. That’s a mixed blessing. They’re going to come closer to
representing a class’s state, but then the question will arise: why
have both that and instance variables of classes? It’s not
impossible to answer that, but it’s not a bad question.
I guess that the only difference between class variables and class
instance variables in 1.9 is that the former are visible in instance
methods.
It seems to me that the baby is in the bathwater here, the baby being
uses which rely on inheritance of class variables.
It might be interesting to see what happens when rails steps up to
ruby 1.9. It appears to use class variables pretty extensively. I’m
not sure how much it relies on class variable inheritance though.
… Long example justifying inheritable class variables snipped.
in short there are a million reasons to use class variables revolving
around the glitch that class inheritance in ruby does not provide a
straightforward way for child classes to have any sort of setup step
performed and that class variables let you perform that setup once in
a way that is inherited and yet settable by client code.
Not much to disagree with here.
An interesting side note. In Smalltalk, the declaration of variables
doesn’t initialize them. Conventionally, class variables are
initialized in a class method called initialize. I’m a bit rusty on
this but IIRC this is something which has to be done manually after
defining the method and before instantiating any instances of the
class.
anyhow i really think it’s the syntax, not the concept, that is cause
for confusion.
To summarize what I’ve said, I think it’s a combination of the
semantics and the implementation.
ps. there are a lot of powerful concepts in ruby that are mostly mis-
understood: closures, mixins, singleton classes, callcc, throw/catch,
and lambda abstraction are part of what allows ruby to grow in
usefulness beyond the realm of the ‘mere scripting language’ some
people set out to learn. i would be loathe so any of them vanish.
Amen.
pss. encapsulation is vastly overrated. when is that last time you
used ‘protected’ in any ruby code?
Careful, Ara, the thought police are lurking!
–
Rick DeNatale
My blog on Ruby
http://talklikeaduck.denhaven2.com/