Является ли 'yield self' таким же, как instance_eval? - PullRequest
20 голосов
/ 15 сентября 2009

Есть ли разница, если вы определяете Foo с instance_eval:. , .

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

. , , или с «самоотдачей»:

class Foo
  def initialize
    yield self if block_given?
  end
end

В любом случае вы можете сделать это:

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

То есть «yield self» означает, что блок после Foo.new всегда вычисляется в контексте класса Foo.

Это правильно?

Ответы [ 3 ]

15 голосов
/ 15 сентября 2009

Ваши две части кода делают совершенно разные вещи. Используя instance_eval, вы оцениваете блок в контексте вашего объекта. Это означает, что использование def определит методы для этого объекта. Это также означает, что вызов метода без получателя внутри блока вызовет его для вашего объекта.

При выдаче себя вы передаете себя в качестве аргумента блоку, но поскольку ваш блок не принимает никаких аргументов, он просто игнорируется. Так что в этом случае самоотдача делает то же самое, что и ничто. def здесь ведет себя точно так же, как и def вне блока, выход на себя фактически не меняет то, для чего вы определяете метод. Что вы могли бы сделать:

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

Разница с instance_eval заключается в том, что вы должны явно указать получателя.

Изменить, чтобы уточнить:

В версии с yield, obj в блоке будет полученным объектом, который в данном случае является вновь созданным экземпляром Foo. Хотя self будет иметь то же значение, что и вне блока. С версией instance_eval self внутри блока будет только что созданный экземпляр Foo.

8 голосов
/ 15 сентября 2009

Они разные. yield(self) не меняет значение self внутри блока, в то время как instance_eval(&block) меняет.

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
5 голосов
/ 15 сентября 2009

Вы можете просто удалить ключевое слово self

class Foo
  def initialize
    yield if block_given?
  end
end

Обновление от комментариев

Использование yield на мой вкус немного нововато, особенно при использовании за пределами irb.

Однако существует большая и значительная разница между подходом instance_eval и подходом yield , проверьте этот фрагмент:

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>

Проверьте это также:

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"

Как видите, разница в том, что первый добавляет одноэлементный метод foo к инициализируемому объекту, а последний добавляет приватный метод ко всем экземплярам класса Object.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...