I play with metaprogramming in ruby and I have a question. I have a class:
class Klass
class << self
@x = "yeah"
end
end
b = Klass.new
a = class << Klass; self; end
a.instance_eval "@x" #=> yeah
Klass.instance_eval "@x" #=> nil
Why? In variable a
I have a singleton class, right? And Klass.instance_eval
exec in context of a singleton class:
Klass.instance_eval "def yeah; puts 10; end"
Klass.yeah #=> 10
Also, Klass
in interpreter points to context of class, yes? And a
points to context of a singleton class?
And which indicates a.class_eval
and a.instance_eval
? I do:
a.instance_eval "def pops; puts 0; end"
a.class_eval "def popsx; puts 1; end"
a.pops #=> 0
a.popsx # FAIL
Klass.pops # FAIL
Klass.popsx #=> 1
b.pops; b.popsx # DOUBLE FAIL
and I do not understand this. Thanks!
First, while it seems like eigentclass is used by some people singleton class is more common term. Singleton class contains object-specific behavior for an object in Ruby. You can't create other instances of that class except the original object this singleton class belongs to.
Speaking about defining of methods inside different types of eval this article introduces nice rule for methods defined in instance_eval
and class_eval
:
Use ClassName.instance_eval to define class methods. Use ClassName.class_eval to define instance methods.
That pretty much describes the situation.
There was a huge write-up about classes that are instances of Class class, their singleton classes that are subclasses of Class class and some other crazy stuff (not that much related to the problem). But as your question can be easily applied to regular objects and their classes (and it makes things much easier to explain), I decided to remove that all (though, you can still see that stuff in revisions history of the answer).
Let's look at regular class and instance of that class and see how that all works:
class A; end
a = A.new
Method definitions inside different types of eval:
# define instance method inside class context
A.class_eval { def bar; 'bar'; end }
puts a.bar # => bar
puts A.new.bar # => bar
# class_eval is equivalent to re-opening the class
class A
def bar2; 'bar2'; end
end
puts a.bar2 # => bar2
puts A.new.bar2 # => bar2
Defining object-specific methods:
# define object-specific method in the context of object itself
a.instance_eval { def foo; 'foo'; end }
puts a.foo # => foo
# method definition inside instance_eval is equivalent to this
def a.foo2; 'foo2'; end
puts a.foo2 # => foo2
# no foo method here
# puts A.new.foo # => undefined method `foo' for #<A:0x8b35b20>
Let's now look at singleton class of object a
:
# singleton class of a is subclass of A
p (class << a; self; end).ancestors
# => [A, Object, Kernel, BasicObject]
# define instance method inside a's singleton class context
class << a
def foobar; 'foobar'; end;
end
puts a.foobar # => foobar
# as expected foobar is not available for other instances of class A
# because it's instance method of a's singleton class and a is the only
# instance of that class
# puts A.new.foobar # => undefined method `foobar' for #<A:0x8b35b20>
# same for equivalent class_eval version
(class << a; self; end).class_eval do
def foobar2; 'foobar2'; end;
end
puts a.foobar2 # => foobar2
# no foobar2 here as well
# puts A.new.foobar2 # => undefined method `foobar2' for #<A:0x8b35b20>
Now let's look at instance variables:
# define instance variable for object a
a.instance_eval { @x = 1 }
# we can access that @x using same instance_eval
puts a.instance_eval { @x } # => 1
# or via convenient instance_variable_get method
puts a.instance_variable_get(:@x) # => 1
And now to instance variables inside class_eval
:
# class_eval is instance method of Module class
# so it's not available for object a
# a.class_eval { } # => undefined method `class_eval' for #<A:0x8fbaa74>
# instance variable definition works the same inside
# class_eval and instance_eval
A.instance_eval { @y = 1 }
A.class_eval { @z = 1 }
# both variables belong to A class itself
p A.instance_variables # => [:@y, :@z]
# instance variables can be accessed in both ways as well
puts A.instance_eval { @y } # => 1
puts A.class_eval { @z } # => 1
# no instance_variables here
p A.new.instance_variables # => []
Now if you replace class A
with class Class
and object a
with object Klass
(that in this particular situation is nothing more than instance of class Class
) I hope you'll get explanation to your questions. If you still have some feel free to ask.
It's hard to completely answer your question (for a thorough explanation of Ruby's class model, look at Dave Thomas' excellent presentation), nevertheless:
With class_eval, you actually define instance methods - it's as if you were inside the body of the class. For example:
class Klass
end
Klass.class_eval do
def instance_method
puts 'instance method'
end
end
obj = Klass.new
obj.instance_method # instance method
With instance_eval, you actually define class methods - it's as if you were inside the body of the singleton (eigenclass) class of the given object (nota bene that classes are objects too in Ruby). For example:
Klass.instance_eval do
def class_method
puts 'class method'
end
end
Klass.class_method # class method
And in your case:
Klass.instance_eval "@x"
does not work because @x
is not part of Klass, it's part of Klass' singleton class:
class Klass
class << self
@x = "yeah"
end
puts @x
end
# prints nothing
a.instance_eval "@x"
works fine because you evaluate "@x" in the context of the a
singleton class that is connected with the singleton class of Klass class in which you defined the @x
instance variable. How singleton classes can be interconnected can be illustrated by this example:
class Foo
end
f = class << Foo; self; end
g = class << Foo; self; end
f.instance_eval "def yeah; puts 'yeah'; end"
g.yeah # yeah
g.instance_eval "def ghee; puts 'ghee'; end"
f.ghee # ghee
Klass.instance_eval "def yeah; puts 10; end"
defines a 'normal' class method. Therefore Klass.yeah
works fine (see Klass.class_method
in the previous example).
a.instance_eval "def pops; puts 0; end"
defines a class method on the a
singleton class. Therefore, a.pops
actually means calling the pops
class method (again, it's as if calling Klass.class_method
).
a.popsx
does not work because you would first have to create an instance of a
to be able to call popsx on it (but is not possible to create a new instance of a singleton class).
Klass.pops
does not work because there isn't any pops
method defined in Klass' singleton class (pops
is defined in a
's singleton class).
Klass.popsx
works because with a.class_eval "def popsx; puts 1; end"
you have defined the popsx instance method which you then call on the Klass object. It is, in a way, similar to this example:
class Other
end
o = Other.new
Other.class_eval "def yeah; puts 'yeah'; end"
o.yeah # yeah
Hope it helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With