Ruby的Enumerator对象如何在内部迭代器上进行外部迭代?

时间:2012-06-15 19:45:37

标签: ruby iterator enumerator

根据Ruby的文档,如果没有为eachto_enum方法提供目标方法,则Enumerator对象使用enum_for方法(枚举)。现在,让我们以下面的猴子补丁及其枚举器为例

o = Object.new
def o.each
    yield 1
    yield 2
    yield 3
end
e = o.to_enum

loop do
  puts e.next
end

假设Enumerator对象在调用each时使用next方法进行回答,每次调用each时,对next方法的调用如何? ? Enumeartor类是否预先加载o.each的所有内容并为枚举创建本地副本?或者是否有某种Ruby魔法在每个yield语句中挂起操作,直到在enumeartor上调用next为止?

如果制作了内部副本,它是否是深层副本?那些可以用于外部枚举的I / O对象呢?

我正在使用Ruby 1.9.2。

1 个答案:

答案 0 :(得分:9)

这并不完全是魔术,但它仍然是美丽的。而不是制作某种类型的副本,Fiber用于首先在目标可枚举对象上执行each。收到each的下一个对象后,Fiber会生成此对象,从而将控制权返回到最初恢复Fiber的位置。

它很漂亮,因为这种方法不需要可枚举对象的副本或其他形式的“备份”,因为可以想象通过例如在可枚举上调用#to_a来获取。使用光纤的协作调度允许在需要时准确地切换上下文,而无需保留某种形式的前瞻。

这一切都发生在Enumerator的{​​{3}}。一个显示大致相同行为的纯Ruby版本可能如下所示:

class MyEnumerator
  def initialize(enumerable)
    @fiber = Fiber.new do
      enumerable.each { |item| Fiber.yield item }
    end
  end

  def next
    @fiber.resume || raise(StopIteration.new("iteration reached an end"))
  end
end

class MyEnumerable
  def each
    yield 1
    yield 2
    yield 3
  end
end

e = MyEnumerator.new(MyEnumerable.new)
puts e.next # => 1
puts e.next # => 2
puts e.next # => 3
puts e.next # => StopIteration is raised