Is 'yield self' the same as instance_eval?

后端 未结 3 434
自闭症患者
自闭症患者 2020-12-24 02:47

Is there any difference if you define Foo with instance_eval: . . .

class Foo
    def initialize(&block)
      instance_eval(&block)         


        
相关标签:
3条回答
  • 2020-12-24 03:30

    They are different. yield(self) does not change the value of self inside the block, while instance_eval(&block) does.

    class Foo
      def with_yield
        yield(self)
      end
    
      def with_instance_eval(&block)
        instance_eval(&block)
      end
    end
    
    f = Foo.new
    
    f.with_yield do |arg|
      p self
      # => main
      p arg
      # => #<Foo:0x100124b10>
    end
    
    f.with_instance_eval do |arg|
      p self
      # => #<Foo:0x100124b10>
      p arg
      # => #<Foo:0x100124b10>
    end
    
    0 讨论(0)
  • 2020-12-24 03:31

    You just can drop the self keyword

    class Foo
      def initialize
        yield if block_given?
      end
    end
    

    Update from comments

    Using yield there is a bit new to my taste, specially when used outside irb.

    However there is a big and significant difference between instance_eval approach and yield approach, check this snippet:

    class Foo
      def initialize(&block)
        instance_eval(&block) if block_given?
      end
    end
    x = Foo.new { def foo; 'foo'; end }            
    #=> #<Foo:0xb800f6a0>                                            
    x.foo #=> "foo"                                                        
    z = Foo.new  #=> #<Foo:0xb800806c>                                            
    z.foo #=>NoMethodError: undefined method `foo' for #<Foo:0xb800806c>
    

    Check this one as well:

    class Foo2
      def initialize
        yield if block_given?
      end
    end
    x = Foo2.new { def foo; 'foo'; end } #=> #<Foo:0xb7ff1bb4>
    x.foo #=> private method `foo' called for #<Foo2:0xb8004930> (NoMethodError)
    x.send :foo => "foo"
    z = Foo.new  #=> #<Foo:0xb800806c> 
    z.send :foo => "foo"
    

    As you can see the difference is that the former one is adding a singleton method foo to the object being initialized, while the later is adding a private method to all instances of Object class.

    0 讨论(0)
  • 2020-12-24 03:35

    Your two pieces of code do very different things. By using instance_eval you're evaluating the block in the context of your object. This means that using def will define methods on that object. It also means that calling a method without a receiver inside the block will call it on your object.

    When yielding self you're passing self as an argument to the block, but since your block doesn't take any arguments, it is simply ignored. So in this case yielding self does the same thing as yielding nothing. The def here behaves exactly like a def outside the block would, yielding self does not actually change what you define the method on. What you could do is:

    class Foo
      def initialize
        yield self if block_given?
      end
    end
    x = Foo.new {|obj| def obj.foo() 'foo' end}
    x.foo
    

    The difference to instance_eval being that you have to specify the receiver explicitly.

    Edit to clarify:

    In the version with yield, obj in the block will be the object that is yielded, which in this case is is the newly created Foo instance. While self will have the same value it had outside the block. With the instance_eval version self inside the block will be the newly created Foo instance.

    0 讨论(0)
提交回复
热议问题