我最近开始阅读“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会暂时离开循环块?
答案 0 :(得分:8)
您将Ruby的yield 语句与Enumerator :: Yielder的yield 方法和Proc的yield 方法混淆。它们的拼写可能相同,但它们完全不同。
yield语句没有接收者。在方法内部,它意味着“立即运行块”。如果未附加块,则会发生错误。它并不总是有一个参数,因为有时你只想运行该块。
def foo
yield :bar
end
foo # LocalJumpError
foo { |x| puts x } # bar
对于一个yielder,yield
几乎总是有一个参数。这是因为它与<<
相同,即“下次有人在我身上调用next
时,给他们这个值”。
Enumerator.new { |yielder| yielder.yield 3 }.next # 3
Enumerator.new { |yielder| yielder << 3 }.next # same thing
我认为使用<<
来避免与yield语句混淆是个好主意。
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