简体   繁体   中英

Why might you call instance_eval (as opposed to class_eval) inside 'initialize'?

  class Observer
    def initialize(&block)
      instance_eval(&block) if block_given?
    end
  end 

I'm wondering what assumption is made here about the type of block that is being used with 'initialize'.

Since instance_eval is called, this means the block is evaluated in the context of the class Observer.

Why would it do that, as opposed to, say, class_eval and what is likely to be the outcome of evaluating a block in the context of the class?

Also, how would this be invoked?

First of all, you can't do something like this:

class Observer
  def initialize(&block)
    class_eval(&block) if block_given?
  end
end

Because class_eval isn't defined for an instance of Observer . It is defined in Module (which Class descends from). We'll come back to class_eval later.

The reason to use the above idiom is often to allow block initialization:

x = Observer.new do
  add_event(foo)
  some_other_instance_method_on_observer
  self.some_attribute = something
end

Plus, you can add methods to a given instance of the class:

foo = Observer.new do
  def foo
    'foo'
  end
end

foo.foo  # => "foo"

You can accomplish roughly the same thing without instance_eval :

class Foo
  def initialize
    yield self if block_given?
  end
end

foo = Foo.new do |x|
  x.add_event(foo)
  x.some_other_instance_method_on_observer
  x.self.some_attribute = something
end

But that doesn't give you the ability to add methods. If you were to do this:

foo = Foo.new do
  def foo
    'foo'
  end
end

foo.foo  # => "foo"

It seems to work, right? But what you've actually done is to add the foo method to everything, because self is set to the "main" object. It's equivalent to simply defining the method outside of the block. They get added as instance methods to Object , so they work on everything.

Now, as promised, a brief return to class_eval . You could do something like this:

class Observer
  def initialize(&block)
    class.class_eval(&block) if block_given?
  end
end

But then you open up the entire class:

x = Observer.new { def foo; 'foo'; end }
x.foo                                      # => "foo"
y = Observer.new
y.foo                                      # => "foo"

This isn't typically what we want to do. Plus, self will be the class, not the instance. This makes it useless for the block initialization as demonstrated above.

One use-case would be setting the state of the observer within its context.

Perhaps

o = Observer.new do
      listen_to(<some object>)
      report_to(<something>)
    end

Such use would not work with a class_eval, which could only access class state

Initialize is an instance method. It's executed in the context of an instance, not the class itself. Thus instance_eval causes the block to execute in the context of the instance as well.

There is no such thing as class_eval in a normal object instance — it's only defined for classes.

Simply because the initialize method is an instance method and class_eval is defined for objects of type Class which means they can be executed only in class methods or inside the class body.

So the following snippet of code will raise an error:

"".class_eval{methods} #=> NoMethodError: undefined method `class_eval' for "":String

While this one will just work:

s.class.class_eval{methods} #=> ["methods", "respond_to?", "module_eval", "class_variables", "dup", "instance_variables", "protected_instance_methods", "__id__", "public_method_defined?", "eql?", "object_id", "const_set", "id", "singleton_methods", "send", "class_eval", "taint", "include?", "private_instance_methods", "frozen?", "instance_variable_get", "private_method_defined?", "__send__", "instance_of?", "name", "to_a", "autoload", "type", "new", "protected_methods", "instance_eval", "display", "instance_method", "instance_variable_set", "kind_of?", "protected_method_defined?", "extend", "const_defined?", "to_s", "ancestors", "public_class_method", "allocate", "class", "<=>", "hash", "<", "tainted?", "private_methods", "==", "instance_methods", "===", "class_variable_defined?", ">", "nil?", "untaint", "constants", ">=", "is_a?", "autoload?", "<=", "inspect", "private_class_method", "const_missing", "method", "clone", "=~", "public_instance_methods", "public_methods", "method_defined?", "superclass", "instance_variable_defined?", "equal?", "freeze", "included_modules", "const_get"]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM