Why does instance_eval() define a class method when called on a class?
Asked Answered
E

2

6
Foo = Class.new
Foo.instance_eval do
  def instance_bar
    "instance_bar"
  end
end
puts Foo.instance_bar       #=> "instance_bar"
puts Foo.new.instance_bar   #=> undefined method ‘instance_bar’

My understanding is that calling instance_eval on an object is supposed to allow you to define an instance variable or method for that object.

But in the example above, when you call it on class Foo to define the instance_bar method, instance_bar becomes a class method that can be invoked with "Foo.instance_bar". It is clear that this code has not created an instance method because Foo.new.instance_bar results in "undefined method ‘instance_bar’".

Why does instance_eval define a class method rather than an instance method in this context?

Exum answered 23/5, 2009 at 2:22 Comment(0)
B
10

x.instance_eval changes your context so self evaluates to x.

This allows you to do many things, including defining instance variables and instance methods but only for x.

 x = Object.new
 y = Object.new

 # define instance variables for x and y
 x.instance_eval { @var = 1 }
 y.instance_eval { @var = 2 }

 # define an instance method for all Objects
 class Object
   def var
     @var
   end
 end

 x.var #=> 1
 y.var #=> 2

Ruby lets you define instance methods for an object in a couple places. Normally, one defines them in a class, and those instance methods are shared among all instances of that class (like def var above).

However, we can also define an instance method for just a single object:

# here's one way to do it
def x.foo
  "foo!"
end
# here's another
x.instance_eval do
  # remember, in here self is x, so bar is attached to x.
  def bar
    "bar!"
  end
end

Even though x and y have the same class, they don't share these methods, since they were only defined for x.

x.foo #=> "foo!"
x.bar #=> "bar!"
y.foo #=> raises NoMethodError
y.bar #=> raises NoMethodError

Now in ruby, everything's an object, even classes. Class methods are just instance methods for that class object.

# we have two ways of creating a class:
class A 
end
# the former is just syntatic sugar for the latter
B = Class.new

# we have do ways of defining class methods:

# the first two are the same as for any other object
def A.baz
  "baz!"
end
A.instance_eval do
   def frog
     "frog!"
   end
end

# the others are in the class context, which is slightly different
class A
  def self.marco
    "polo!"
  end
  # since A == self in here, this is the same as the last one.
  def A.red_light
    "green light!"
  end

  # unlike instance_eval, class context is special in that methods that
  # aren't attached to a specific object are taken as instance methods for instances
  # of the class
  def example
     "I'm an instance of A, not A itself"
  end
end
# class_eval opens up the class context in the same way
A.class_eval do
  def self.telegram
    "not a land shark"
  end
end

Note again, that all these methods are A-specific, B doesn't get access to any of them:

A.baz #=> "baz!"
B.telegram #=> raises NoMethodError

The important thing to take away from here is that class methods are just instance methods of an object of class Class

Bertrambertrand answered 23/5, 2009 at 3:0 Comment(6)
When you wrote "A.instance_eval do def A.frog 'frog!' end end" is it significant that you said "A.frog"? Or could you have also said "self.frog" there? Later on when you define "A.class_eval do def self.telegram 'not a land shark' end end", you say "self.telegram" rather than "A.telegram" - again, are these interchangeable or is it significant that you use "A." for instance_eval and "self." for class_eval?Exum
You say "The important thing to take away from here is that class methods are just instance methods of an object of class Class". So classes A and B here are actually instances even before we instantiate them (instances of class Class)? I thought we have to call the new() method before we get an instance.Exum
Heh, I screwed up a little. The A. in A.frog wasn't necessary (I've fixed it) - so see the parallelism between x and A there.Bertrambertrand
within a class-context block (class X...end or X.class_eval {...}) def self.foo and def X.foo are equivalent, because self == X in that context.Bertrambertrand
A and B are instances of class Class. Here, class A ; end produces the same result as A = Class.new.Bertrambertrand
class A ... end defines A as an instantiation of Class.Bertrambertrand
V
2

The purpose of 'instance_eval' is to extend objects, but the purpose of 'class_eval' is to extend classes. And because classes are also objects, you can apply instance_eval on classes.

I guess that extending of classes is just more understandable in classic OOP. Dynamic languages allow us to easily specify behaviour of particular objects. Fact that each object can have own behaviour adds a lot of flexibility designing an application. Not only data can vary for objects of the same class. Two humans differ not only because they were born in different years, not only because they have different parents but they can think different and thus behave different.

Ability to change the behaviour of each object is fundamental. It exists in many languages.

Thinking about instance_eval think about objects first. Then you'll realize that classes are also objects - objects which additional purpose to create new objects, to hold description of common behaviour (methods). You can not only use definition of a class, but can also assign class to a variable, pass class as an argument, call method on a class, program a class!

I would recommend articles written by Yehuda Katz and Yugui to dive deeper into it:

Vacillatory answered 29/4, 2014 at 7:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.