Class Variables in EigenClass

J

JP Billaud

I am using Ruby 1.8.7 and I bumped into a weird behavior regarding
eigenclass of Class object.

Below, I am declaring a class variable inside the eigenclass of Class A.
However for some reasons when I print out the class variables of A and
B, it seems the class variable @@test is accessible for both.

===

def eigen
class << self
self
end
end

class A
end

class B
end

egA = A.send :eigen
puts egA.object_id

egB = B.send :eigen
puts egB.object_id

egA.class_eval do
@@test = 5
end

puts "A Class Variables"
puts A.class_variables

puts "B Class Variables"
puts B.class_variables

===

-607970628
-607970668
A Class Variables
@@test
B Class Variables
@@test

Any idea?

Thanks,
 
J

JP Billaud

Actually it seems that for some reasons the @@test class variables ended
up in the Object class:

puts Object.class_variables

Anyway I am still confused about this... Help would very appreciated.

Thanks,
 
G

Gary Wright

Actually it seems that for some reasons the @@test class variables = ended=20
up in the Object class:
=20
puts Object.class_variables
=20
Anyway I am still confused about this... Help would very appreciated.

The snarky answer is: "Don't use class variables". They really are hard =
to understand and generally don't have the semantics you expect or need.

The non-snarky answer is that when you do:

egA.class_eval do
@@test =3D 5
end

Ruby does *not* evaluate '@@test' relative to self, which would be egA, =
but instead evaluates it relative to the lexical scope that is in =
effect, which in your code is the top level scope. Class variables =
evaluated at the top level scope are instantiated within Object.

Most people expect class variables to only be visible to the class they =
are instantiated in but they are actually visible to their 'home' class =
as well as all subclasses. Since every class is a subclass of Object, =
in your example, @@test becomes visible in every class and in every =
instance.

Gary Wright=
 
J

JP Billaud

Thanks Gary for the explanation. I was hoping the self logic would have
applied in this case since I feel this is the intuitive way to think
about it...

Another question about class variables actually. My understanding of
class variables lookup is that it is not different from the methods
lookup. Indeed, an instance object look at its class's class variables
and then go through superclass and module all the way up to BasicObject.

Now why does not it apply to Classes object:

===
class Class
@@test = "test"
end

class B
def self.print
puts @@test
end
end

B.print
===

In this case B.print fails with:

module.rb:7:in `print': uninitialized class variable @@test in B
(NameError)
from module.rb:11

Since B's class is Class that should be fine. Obviously I must be
missing something here.

Thanks,
Jean-Pascal Billaud
 
G

Gary Wright

Another question about class variables actually. My understanding of=20=
class variables lookup is that it is not different from the methods=20
lookup.

They are not the same and are closer to the way constants are looked up =
but not quite the same as that either...
Indeed, an instance object look at its class's class variables=20
and then go through superclass and module all the way up to =
BasicObject.

Not really, the lookup is more accurately associated with the lexical =
context of the reference than the dynamic context. In your B.print =
method, the lexical scope is actually the class B and so that is the =
starting point for the class variable resolution. This is distinctly =
different than the way instance variables are resolved.
Now why does not it apply to Classes object:
=20
=3D=3D=3D
class Class
@@test =3D "test"
end
=20
class B
def self.print
puts @@test
end
end
=20
B.print
=3D=3D=3D
=20
In this case B.print fails with:
=20
module.rb:7:in `print': uninitialized class variable @@test in B=20
(NameError)
from module.rb:11
=20
Since B's class is Class that should be fine. Obviously I must be=20
missing something here.


Consider the following:

class A
@@foo =3D "hello"
def foo
@@foo
end
def foo_ieval
42.instance_eval { @@foo }
end
def foo_ceval
Array.class_eval { @@foo }
end
end


puts A.new.foo # "hello"
puts A.new.foo_ieval # "hello"
puts A.new.foo_ceval # "hello"

Array.class_eval { @@foo } # undefined

The lexical scope for foo, foo_ieval, and foo_ceval is the enclosing =
class/end block for A while the lexical scope for the final =
Array.class_eval is the top level. For the three methods, they all =
resolve to the same class variable owned by the class A while the =
class_eval outside of A's definition block resolves to the top level =
class, Object.

Gary Wright=
 
J

JP Billaud

Alright that explains the situation even though I feel that the dynamic
context should apply in this case since class_eval changes self and the
current class. IMO lexical scope should only be useful for local
variables...
 
J

John Mair

Hey,

While what you've said is true for `class_eval` contexts (that they
behave like constants) it doesn't explain the following behaviour, which
the OP may or may not have been getting at:

class C; end

c = C.new

class << c
@@var = 20
end

#=> Warning: class variable access from toplevel singleton method

It appears that the class var is defined on Object. In fact it's working
lexically here too:

$c = C.new

module J
class << $c; @@var = 30; end
end

J.instance_variables #=> [:mad:@var]

Pretty weird

Gary Wright wrote in post #985860:
 
G

Gary Wright

While what you've said is true for `class_eval` contexts (that they=20
behave like constants) it doesn't explain the following behaviour, = which=20
the OP may or may not have been getting at:

I think you just restated the same thing I said.

One thing that you emphasized and which is confusing is that the =
singleton class notation:

class <<object
end

does *not* create a new lexical scope - nor does class_eval or =
instance_eval.

Because there is no new lexical scope from these constructs the =
interpretation of class variables doesn't change when these constructs =
are used, and that is generally not what is expected.

Gary Wright
 
J

John Mair

One thing that you emphasized and which is confusing is that the
singleton class notation:

class <<object
end

does *not* create a new lexical scope - nor does class_eval or
instance_eval.

No, it *does* create a new lexical scope -- did you test it ? :)

o = Object.new
j = 10

class << o
puts j
end

NameError: undefined local variable or method `j'

This was the point of my post -- that you can't simply use the 'lexical
scope' argument (as you can with class_eval); that this is a genuine
quirk of class variables in ruby :)
 
G

Gary Wright

No, it *does* create a new lexical scope -- did you test it ? :)

o = Object.new
j = 10

class << o
puts j
end

I think it was my sloppy terminology that was confusing. My intent was that the phrase 'lexical scope' would refer to the scope that was relevant to constant and class variable resolution. As you've pointed out, that phrase just is not specific enough as there is a lexical scope created for local variables by the singleton class notation (and blocks). So there are at least two different types of lexical scopes that affect name resolution of variables and constants. I'm not sure if there is any standard terminology used to differentiate them. Perhaps that is part of the confusion.

Gary Wright
 
S

Sony S.

Jean-Pascal Billaud wrote in post #985843:
Any idea?

Thanks,

You can use class_variable_set:

def eigen
class << self
self
end
end

class A
end

class B
end

egA = A.send :eigen
puts egA.object_id

class A
@@in_A = 'A'
end

egA.send:)class_variable_set, :mad:@in_egA, 'egA') # The secret!

puts "A Class Variables"
puts A.class_variables #=> @@in_A

puts "egA Class Variables"
puts egA.class_variables #=> @@in_egA

puts "B Class Variables"
puts B.class_variables #=> empty
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top