tags:

views:

361

answers:

3

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

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

. . . or with 'yield self':

class Foo
  def initialize
    yield self if block_given?
  end
end

In either case you can do this:

x = Foo.new { def foo; 'foo'; end }
x.foo

So 'yield self' means that the block after Foo.new is always evaluated in the context of the Foo class.

Is this correct?

+4  A: 

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.

khelll
"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 that method to all instances of class Foo2" Actually, the latter is adding the method to Object (and in real ruby (as opposed to irb) it will add it as a private method so you can't do x.foo or z.foo, just foo). In other words the def behaves exactly as if you had written it outside the block (unless the method does not yield of course, in which case nothing happens).
sepp2k
You are absolutely true, thanks
khelll
A: 

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
August Lilleaas
The second `p arg` should print `nil`, not `#<Foo:0x100124b10>`.
sepp2k
In 1.8.7, the Foo instance is printed. I thought it would be nil as well, not sure why it isn't.
August Lilleaas
In 1.9 nil is printed. I can't see any explanation for 1.8.7 printing the Foo instance. Are you sure you didn't misread the output?
sepp2k
coderjoe
+5  A: 

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.

sepp2k
In your "Edit to clarify", don't you mean that self is yielded to the obj in the block? Maybe I'm just reading it a different way but I see the object being initialized, self is yielded to the block as 'obj' and then inside the block the method foo is defined on self through obj.
uzo
I'm pretty sure, we mean the same thing. I wrote "the newly created Foo instance" because self inside the initialize method (which is the newly created Foo instance) is not the same as self inside the block and if you just say "self", it's unclear which one you mean.
sepp2k