根据Ruby的文档,如果没有为each
或to_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。
答案 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