枚举器yielder.yield VS Proc.yield

时间:2013-09-18 07:05:39

标签: ruby yield enumerator

我最近开始阅读“Programming Ruby 1.9& 2.0”一书。 它显示了显式枚举器的技巧

triangular_numbers = Enumerator.new do |yielder|
number = 0
count = 1
    loop do
        number += count
        count += 1
        yielder.yield number
    end
end
5.times { print triangular_numbers.next, " " }
puts

我想知道为什么这个yielder.yield会暂时离开循环并返回 number 的值,直到创建下一个枚举器对象。它似乎与循环块内的产量的通常情况不同。我查看了APIdock,发现Proc.yield()的源代码与Proc.call()相同。对于Enumerator类中的Yielder对象,Yielder已覆盖yield()。但为什么yielder.yield会暂时离开循环块?

参考: APIdock Yielder yield()Ruby MRI rb_proc_call

2 个答案:

答案 0 :(得分:8)

您将Ruby的yield 语句与Enumerator :: Yielder的yield 方法和Proc的yield 方法混淆。它们的拼写可能相同,但它们完全不同。

声明

yield语句没有接收者。在方法内部,它意味着“立即运行块”。如果未附加块,则会发生错误。它并不总是有一个参数,因为有时你只想运行该块。

def foo
  yield :bar
end
foo # LocalJumpError
foo { |x| puts x } # bar

枚举:: Yielder

对于一个yielder,yield几乎总是有一个参数。这是因为它与<<相同,即“下次有人在我身上调用next时,给他们这个值”。

Enumerator.new { |yielder| yielder.yield 3 }.next # 3
Enumerator.new { |yielder| yielder << 3 }.next # same thing

我认为使用<<来避免与yield语句混淆是个好主意。

PROC

Procs和lambdas基本上都是函数。 yield这里的意思与call相同,即“只需调用函数”。您可以给它一个参数或不参数,具体取决于proc的定义方式。没什么好看的。

proc { |x| puts x }.yield(:bar) # bar
proc { |x| puts x }.call(:bar) # same thing as previous line

我认为使用call来避免与yield语句混淆是个好主意。

答案 1 :(得分:1)

我也在书中偶然发现了这个例子。 在考虑了示例如何工作并浏览Ruby文档之后,我发现了我认为是Enumerator在场景后面使用的Fiber类:

http://www.ruby-doc.org/core-2.0/Fiber.html

光纤概念实现了“轻量级协同并发”,这非常有趣,并不难理解,更重要的是它与调用块或处理线程控制的其他“产量”不同。

我认为Enumerator有一个Fiber对象,在其中传递给块。然后看起来每次在Enumerator上调用“next”时,它会在Fiber对象上调用“resume”以允许它计算下一个数字,当块调用Fiber上的“yield”时,控件返回到“下一步“方法。等等。

这是我对Enumerator可能实现的版本(当然,只有本书中的讨论部分):

class MyExplicitEnumerator

  def initialize (&block)
    @yielder = Fiber.new { block.call Fiber }
  end

  def next
    @yielder.resume
  end

end

e = MyExplicitEnumerator.new do |yielder| 
    number = 1
    loop do
      yielder.yield number
      number += 1
    end
  end

p e.next
p e.next

# output
# 1
# 2