Hi all,
I’ve been writing a library that one uses by including it in one’s own
classes. The purpose of the library is not important - the problem is to
do with including modules in classes, in particular to do with class
variables.
Exhibit A:
irb(main):001:0> module Mod
irb(main):002:1> @@foo = "Mod"
irb(main):003:1> end
=> "Mod"
irb(main):004:0> class KlassA
irb(main):005:1> include Mod
irb(main):006:1> end
=> KlassA
irb(main):007:0> class KlassB
irb(main):008:1> include Mod
irb(main):009:1> @@foo = "B"
irb(main):010:1> end
=> "B"
irb(main):011:0> class KlassA
irb(main):012:1> puts @@foo
irb(main):013:1> end
B
=> nil
If I create a class variable from within a module, that variable is
shared between all classes that include it. So far, so good - this is
useful.
Exhibit B:
irb(main):001:0> module Mod
irb(main):002:1> end
=> nil
irb(main):003:0> class KlassA
irb(main):004:1> include Mod
irb(main):005:1> @@foo = "A"
irb(main):006:1> end
=> "A"
irb(main):007:0> class KlassB
irb(main):008:1> include Mod
irb(main):009:1> @@foo = "B"
irb(main):010:1> end
=> "B"
irb(main):011:0> class KlassA
irb(main):012:1> puts @@foo
irb(main):013:1> end
A
=> nil
If I create a class variable from within a class, then no matter how
many modules or superclasses the classes have in common, it doesn’t get
shared. No surprises thus far - POLS, and all.
Exhibit B.2:
irb(main):014:0> module Mod
irb(main):015:1> @@foo = "Mod"
irb(main):016:1> end
=> "Mod"
irb(main):017:0> class KlassA
irb(main):018:1> puts @@foo
irb(main):019:1> end
A
=> nil
irb(main):020:0> class KlassB
irb(main):021:1> puts @@foo
irb(main):022:1> end
B
=> nil
Even if I subsequently define the variable in the module, it still
doesn’t get shared. This is probably also POLS - having variables
clobber each other later is somewhat surprising.
Now, here comes the fun bit. When you include a module in a class, the
module’s append_features method gets called, and the class passed as an
argument. Watch this:
irb(main):001:0> module Mod
irb(main):002:1> def self.append_features(aModule)
irb(main):003:2> super
irb(main):004:2> aModule.instance_eval do
irb(main):005:3* @@foo = aModule.name
irb(main):006:3> end
irb(main):007:2> end
irb(main):008:1> end
=> nil
irb(main):009:0> class KlassA
irb(main):010:1> include Mod
irb(main):011:1> end
=> KlassA
irb(main):012:0> class KlassB
irb(main):013:1> include Mod
irb(main):014:1> end
=> KlassB
irb(main):015:0> class KlassA
irb(main):016:1> puts @@foo
irb(main):017:1> end
KlassB
=> nil
Somehow, even though the class variable is defined within the context of
the class (via instance_eval) it still gets shared, contrary to the
example of Exhibit B. This is frustrating me, because I want my library
to be able to define a class variable that belongs to only one class,
and I can’t find a way to do it. Currently, I’m doing this:
irb(main):001:0> module Mod
irb(main):002:1> @@foo = {}
irb(main):003:1> def self.append_features(aModule)
irb(main):004:2> super
irb(main):005:2> aModule.module_eval do
irb(main):006:3* @@foo[id] = aModule.name
irb(main):007:3> end
irb(main):008:2> end
irb(main):009:1> end
=> nil
irb(main):010:0> class KlassA
irb(main):011:1> include Mod
irb(main):012:1> end
=> KlassA
irb(main):013:0> class KlassB
irb(main):014:1> include Mod
irb(main):015:1> end
=> KlassB
irb(main):016:0> class KlassA
irb(main):017:1> puts @@foo[id]
irb(main):018:1> end
KlassA
=> nil
It works, but it’s messy and hackish and breaks encapsulation - if you
know the class’s id, you can get at its “class variable”. Can anyone see
a better way to do this, and/or explain to me why Ruby behaves this way
in the first place?
Tim Bates