Module Eval Syntax


#1

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?


#2

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 :wink: