Module Eval Syntax

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?

···

--
John Long
http://wiseheartdesign.com

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

  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:

···

from :0

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk