I’ve been playing a lot with class variables (aka ‘the double at’) lately. They’re very interesting. How comfortable are you with them? If I were to write some Pickaxe 101 Ruby you’d be okay, right? I mean, some stuff basically off page 33. Okay.
class Auto def how_many_wheels @@wheels end end class Car < Auto @@wheels = 4 end class Motorsickle < Auto @@wheels = 2 end
We’re setup. What happens when I do this:
>> hammer = Motorsickle.new => #<Motorsickle:0x5935a8> >> hammer.how_many_wheels
Will it print 2?
NameError: uninitialized class variable @@wheels in Auto
Of course not. Though this would work with instance variables, our Auto class knows nothing about the class variables Car and Motorsickle keep. Because how_many_wheels is technically being called via Auto and Auto doesn’t have the @@wheels class variable, we get the above exception. Class variables, as the Pickaxe tells us, are “private to a class and its instances.” Remember that.
Let’s give Auto some @@wheels love.
class Auto @@wheels = 0 def how_many_wheels @@wheels end end class Car < Auto @@wheels = 4 end class Motorsickle < Auto @@wheels = 2 end
Now what will we get when we call the same code we called earlier?
>> hammer = Motorsickle.new => #<Motorsickle:0x593530> >> hammer.how_many_wheels => 2
Hey, alright! I mean, no! Wait! Stay in the same context, let’s try Car.
>> blazer = Car.new => #<Car:0x590bb4> >> blazer.how_many_wheels => 2
Crap. Why’s that? It should be 4.
Turns out that when you initialize a class variable in a class and then subclass it, the subclass doesn’t have its own instance of the class variable. Rather, the subclass works directly with the parent’s class variable. So Car and Motorsickle, when playing with what they believe is their own private version of @@wheels, are really acting directly on Auto’s class variable. Remember that, too.
Dr Nic explains this behavior and a workaround in Rails, class_inheritable_accessor, in this post from August.
Oh, but there’s more.
class Computer end class Dell < Computer @@gigarams = 5 def speed @@gigarams end end class Apple < Computer @@gigarams = 90 def speed @@gigarams end end class Computer @@gigarams = 0 def speed @@gigarams end end class Compaq < Computer @@gigarams = 1000 def speed @@gigarams end end
Gigarams, naturally, measure a computer’s speed. Here we see quite clearly and scientifically that Apple computers are all 85 gigarams faster than Dell computers. Compaqs lead the bunch at roughly 1000 gigarams.
In the above code, do we all know what speed is going to print? Let’s run through the list.
>> Dell.new.speed => 5 >> Apple.new.speed => 90 >> Computer.new.speed => 1000 >> Compaq.new.speed => 1000 >> Apple.new.speed => 90
Interesting indeed. This example shows us that class variable references are determined on inherit. Dell and Apple maintain their own @@gigarams class variable while Compaq and Computer share one, as illustrated further up. You could even write code to change Apple’s @@gigarams variable and still be safe. But it’s confusing, yeah? Stay away.
“That computer code isn’t very DRY,” you say. “I’m gonna DRY it up and show this Err bastard how to program.” Go ahead.
class Computer end class Dell < Computer @@gigarams = 5 end class Apple < Computer @@gigarams = 90 end class Computer @@gigarams = 0 def speed @@gigarams end end class Compaq < Computer @@gigarams = 1000 end
“Even though I defined speed after I defined Dell and Apple, I’ll still be able to call it on any instances I create because Ruby has open classes! Perfect!” Yeah, that’s what they all say. But think back: our first example was ruined because Auto didn’t have the @@wheels class variable. Since Computer does the class variable in question, you can be sure running speed on an instance of, say, Dell will give us Computer’s class variable, not Dell’s.
Like this:
>> Dell.new.speed => 1000 >> Computer.new.speed => 1000
Lesson learned. Double at’s are tricky things. It’s worth noting that class variables will not be inherited in Ruby 1.9, but that’s a long way off.
Finally, here’s a non-Rails solution which may interest you.
class Storm class << self attr_accessor :intensity end end class ThunderStorm < Storm self.intensity = 100 end class WinterStorm < Storm self.intensity = 50 end class GlassStorm < Storm end
Nothing new. We’re setting an instance variable on Storm’s singleton class. All its children, when calling the accessor methods, set and read instance variables on their own singleton classes. How clean and deliciously meta.
>> ThunderStorm.intensity => 100 >> WinterStorm.intensity => 50 >> GlassStorm.intensity => nil >> GlassStorm.intensity = 7 => 7 >> GlassStorm.intensity => 7 >> WinterStorm.intensity => 50 >> Storm.intensity => nil
Ah, Ruby.
Wow Ruby seems to be quite difficult there. A quite surprising fact if you come from other languages…
Bart – I don’t think its difficult, it just might not work the way we expect. The simple syntax – @@class_var – doesn’t give the most useful class variable unfortunately, as show above.
The “class << self”-like syntax is difficult. I think its weird syntax, IMHO.
I’m writing a Ruby class variable article for my blog that makes this all seem even more complicated… class variables, class instance variables (as covered here), and the holy grain – class instance variables with inheritance.
I think this stuff is very useful… but with 3 ways of defining class variables, it can indeed be a minefield for most Rubists.
The active_support library (in Rails) has some nice support function, btw.
I have hit serious issues trying to get @@ to do what I think I want it to. It just never quite works the way I expect. I think the “class << self” trick is the nicest soluction I have seen.
Perhaps I shall write a script that inserts ”#Here be Dragons” wherever @@s lurk.
Bart: Don’t mind me, I’m just trying to expose a few dirty little corners. On the whole Ruby is very beautiful. And now that you’ve read the post it’s not so confusing, is it?
Adam: But what if you like dragons? We’ll need something more frightening. # Here be PHP? Java? Perl?
Dr Nic: The main problem with class_inheritable_accessor, to me, is that it only works magic on inherit. If you change the parent’s value after you’ve defined the child, the child will still use the parent’s original value. Maybe that’s intentional, I dunno. What I really want is to use the parent’s current value unless I’ve set the child’s value, even if I change the parent’s value. Something like this:
Then:
Anyway, I’m looking forward to your post on all this voodoo.
@Chris – yeah, I was looking at the code today and noticed this – its a one-time clone of the superclass value at that time.
Chris – another ‘disturbing’ aspect of class_inheritable_accessor is that the accessor methods are added to the Class class, and are thus available to all classes, regardless of hierarchy. Not a real problem, rather just a symptom of the solution.
Your solution totally changed the interface. The buggy examples all used instance methods… Car.new.wheels. But then you switch to class methods – ThunderStorm.intensity. Why did you do that? Also, is the solution as simple as
or is there a better way?
The worst thing about class variables (and I don’t say that lightly; I dislike them intensely on all counts) is that they use the at-sign. The reason that’s bad is that it leads people to think that there’s some connection between class variables and instances variables.
In fact, class variables and instance variables are practically the opposite of each other. Instance variables are strictly per-object: when you see @var, you’re seeing an instance variable that belongs to “self”. Class variables, on the other hand, are rather promiscuous: as the examples here show, they are visible to classes through a hierarchy, and to instances of those classes.
The main effect of the @@var notation has been to make it much harder for people to understand instance variables, especially instance variables of class objects.
It would actually make more sense for class variables to be $var, and globals to be $$var. Class variables have more in common with globals than with instance variables.
I fear that in Ruby 2.0, when class variables are more per-class than they are now, the confusion won’t go away. At that point, class variables will almost be a way to maintain state in a class object… but not quite… and they’ll still have the at-signs. I predict that class variables will still present an obstacle for people who are trying to grasp the concept of a class having its own instance variables.
The “cattr” methods don’t help, because they also suggest a connection between class and instance variables. They also imply that class variables are a suitable mechanism for representing a class’s “attributes”—but since class variables are shared among many objects, and an “attribute” is a property of a particular object, class variables can’t really store attribute values.
1.9: class vars not inherited
Chime in.