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

Sal*_*cha 7 ruby iterator enumerator

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

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

loop do
  puts e.next
end
Run Code Online (Sandbox Code Playgroud)

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

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

我正在使用Ruby 1.9.2.

emb*_*oss 10

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

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

这一切都发生在C代码Enumerator.一个显示大致相同行为的纯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
Run Code Online (Sandbox Code Playgroud)