>> class MyModule; end => nil >> MyModule.class_variables =>  >> MyModule.module_eval do ?> @@test = true >> end => true >> @@test => true >> MyModule.module_eval "@@weird = true" => true >> @@weird NameError: uninitialized class variable @@weird in Object from (irb):8 >> MyModule.class_variables => ["@@weird", "@@test"] >> Object.class_variables => ["@@test"] What in the world is going on here? Why does the block syntax eval differently than the string syntax? And why does @@test get defined on both MyModule and Object?
on 2006-01-26 22:17
on 2006-01-26 23:53
This isn't a direct answer to John's question, but more an expansion of it that came up while I was playing with the original and my theory on what's going on. Considering this IRB: module MyModule; end # => nil MyModule.module_eval do @@mod_eval_var = "mev" end # => "mev" MyModule.instance_eval do @@inst_eval_var = "iev" end # => "iev" def MyModule.mkcv @@sing_meth_var = "smv" end # => nil MyModule.mkcv # => "smv" module MyModule def self.mkcv2 @@cls_sing_meth_var = "csmv" end end # => nil MyModule.mkcv2 # => "csmv" Object.class_variables # => ["@@mod_eval_var", "@@inst_eval_var", "@@sing_meth_var"] Class.class_variables # => ["@@mod_eval_var", "@@inst_eval_var", "@@sing_meth_var"] Module.class_variables # => ["@@mod_eval_var", "@@inst_eval_var", "@@sing_meth_var"] MyModule.class_variables # => ["@@cls_sing_meth_var"] Let me explain what I think I'm seeing here, and see if anyone can steer me right or expand on this behaviour. As far as I can tell, it seems that any time you make a @@class_var outside of an actual class / module definition, you have to be extremely careful that you're actually defining where you think you are. I would guess that any time Ruby sees an @@ variable, it immediately goes to the enclosing class and defines the variable there. Maybe that explains the block behaviour, because although the blocks are evaluated in the module they somehow retain their original enclosing object's class - the top-level object in this case, of class Object. The singleton method definition (def MyModule.mkcv) is actually creating a method on MyModule's singleton class, which is just an instance of Class. The new method becomes an 'instance method' of that singleton class, so that @@sing_meth_var = "smv" results in Class (the #class of the singleton Class instance) getting another class variable. This is demonstrated by: sc = module MyModule class << self; self; end end # => #<Class:MyModule> sc.class # => Class sc.class_variables # => ["@@mod_eval_var", "@@inst_eval_var", "@@sing_meth_var"] If this is true, then it explains why the class variables are present across Object, Class and Module's class variables - since all are instances of Class (and #class_variables shows inherited vars). Since MyModule (an instance of class Module, a superclass of Class) doesn't see these variables, I have to assume they do end up defined on Class. More generally, once a class variable is defined on either Class or Object (each being the class of the other) it is to all intents and purposes defined everywhere, given the class_variables behaviour I mentioned above. The final mkcv2 definition, by virtue of being inside a reopened module definition, *is* defined as a a singleton method on MyModule, but either doesn't go to the singleton class (instead being actually redefined on the Module instance), or else is treated specially (which I somehow doubt but is possible). In any event, this ends up being defined where we'd expect it. To further check out the line between a class and it's singleton class, I did a further experiment: class SomeClazz @@icv = "icv" end # => "icv" SomeClazz.class_variables # => ["@@icv", "@@mod_eval_var", "@@inst_eval_var", "@@sing_meth_var"] Class.class_variables # => ["@@mod_eval_var", "@@inst_eval_var", "@@sing_meth_var"] def SomeClazz.cval @@icv end # => nil SomeClazz.cval NameError: uninitialized class variable @@icv in Object from (irb):90:in `cval' from (irb):92 from :0 class SomeClazz def self.mcval @@icv end end # => nil SomeClazz.mcval # => "icv" This seems to confirm what I said above, and also (to me) is another great reason to stay away from class variables, especially when you're talking about class variables on Class or Module instances. Anyway, this is just theory of course, and especially with the block stuff it seems pretty mysterious - maybe even "surprising" - but it would seem to fit I think - A string eval obviously doesn't close over it's scope so wouldn't be affected by it I guess. I hope someone can shed some more more light on this because it's going to bug me now ;)