4

我最近开始阅读《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的值,直到创建下一个枚举器对象。当循环块内的 yield 时,它似乎与通常的情况不同。我查了APIdock,发现Proc.yield()的源码和Proc.call()是一样的。对于 Enumerator 类中的 Yielder 对象,Yielder 已经覆盖了 yield()。但是为什么yield.yield会暂时离开循环块呢?

参考: APIdock Yielder yield()Ruby MRI rb_proc_call

4

2 回答 2

9

您将 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 语句混淆是一个好主意。

过程

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 语句混淆是一个好主意。

于 2013-09-18T13:50:21.337 回答
1

我也偶然发现了书中的那个例子。在考虑了该示例的工作原理并浏览了 Ruby 文档后,我发现了 Fiber 类,我认为它是 Enumerator 在幕后使用的:

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

Fiber 概念实现了“轻量级协作并发”,这很有趣,并不难理解,更重要的是它不同于调用块或处理线程控制的其他“yield”。

我认为 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
于 2014-06-13T05:35:59.620 回答