On 10/19/07, ara.t.howard <ara.t.howard / gmail.com> wrote:
>
> On Oct 18, 2007, at 11:22 AM, David A. Black wrote:
>
> and ara responds to him but mostly to the general thread:
>
> >
> > I'm on record as saying that class variables are the thing I dislike
> > most about Ruby, so I'll try to explain why.
> >
> > They break encapsulation.
>
> i honestly don't think they do - inheritance breaks encapsulation.
> for example
>
>   class A
>     CONST = 42
>
>     class << self
>       def shared() 42.0 end
>     end
>   end
>
>   class B < A
>   end
>
>   p B::CONST
>   p B::shared
>
> class vars are exactly like this - only with state that happens to be
> resettable.  i don't see anything particular about the way class vars
> behave that is different from constants or singleton (note the
> *single* in singleton methods that can be called on multiple
> 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!

> > They are visible to a weird assortment of
> > objects: class C, instances of C, class D < C, instances of D... all
> > the same variable. This cross-section of objects doesn't have anything
> > in common except that they can all see each other's class variables.
> >
>
> and constants and singleton methods....  of course class singleton
> methods, in an instance context, require a 'self.class' prefix to
> invoke while constants do not but this is not encapsulation - it's
> the breaking of it - instances *should* be allowed to access the
> 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.

> > The prefix @@ makes it appear that they have some connection or
> > kinship with instance variables. Actually, they're almost the
> > opposite. Instance variables represent state on a strictly per-object
> > basis. Class variables cut across many different objects and scopes.
> > I would prefer them to look like $$this, since they're really a
> > kind of restricted global variable rather than an extended instance
> > variable.
>
> i can see that.  still, they *are* associated with an instance - an
> instance of a class hierarchy.  i realize people don't generally seem
> 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:

   1) Ruby class variables look too much like instance variables
because of syntax @@ vs @
   2) 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.

> > That sums up my views. Just so you know: I love Ruby madly :-) This is
> > one of the very few areas where I dissent strongly from the way it's
> > designed.
> >
>
> yeah i see all that.  still, i've deigned quite a few ruby classes
> and i can assure people that they will miss them when they are gone/
> limited.  the reason, curiously, is that classes do not have an
> initialize method/hook.  let me explain: let's say you want to do
> something quite reasonable like so
>
.. 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! <G>

-- 
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/