枚举器如何在Ruby 1.9.1中工作?

这个问题不是关于如何在Ruby 1.9.1中使用枚举器,而是我很好奇它们是如何工作的。 这是一些代码:

class Bunk def initialize @h = [*1..100] end def each if !block_given? enum_for(:each) else 0.upto(@h.length) { |i| yield @h[i] } end end end 

在上面的代码中,我可以使用e = Bunk.new.each ,然后使用e.nexte.next来获取每个连续的元素,但是它究竟是如何暂停执行然后在正确的位置恢复?

我知道如果0.upto的产量被0.upto取代,那么它很容易理解,但这不是这里的情况。 这是一个普通的老yield ,它是如何工作的?

我看了一下enumerator.c,但对我来说这是不可理解的。 也许有人可以在Ruby中提供一个实现,使用光纤,而不是1.8.6样式的基于延续的枚举器,这一切都清楚了吗?

这是一个普通的ruby枚举器,它使用Fibers,并且应该像原始行为一样:

 class MyEnumerator include Enumerable def initialize(obj, iterator_method) @f = Fiber.new do obj.send(iterator_method) do |*args| Fiber.yield(*args) end raise StopIteration end end def next @f.resume end def each loop do yield self.next end rescue StopIteration self end end 

在有人抱怨exception作为流控制之前:真正的Enumerator也会在最后引发StopIteration,所以我只是模仿了原来的行为。

用法:

 >> enum = MyEnumerator.new([1,2,3,4], :each_with_index) => # >> enum.next => [1, 0] >> enum.next => [2, 1] >> enum.to_a => [[3, 2], [4, 3]] 

实际上在你的e = Bunk.new.each中,else子句最初没有被执行。 相反,’if!block_given’子句执行并返回枚举器对象。 枚举器对象在内部保留光纤对象。 (至少这是它在enumerator.c中的样子)

当你调用e.each时,它调用一个枚举器上的方法,该方法在内部使用光纤来跟踪其执行上下文。 此方法使用光纤执行上下文调用Bunk.each方法。 这里的Bunk.each调用确实执行了else子句并产生了值。

我不知道如何实现良率或光纤如何跟踪执行上下文。 我没看过那段代码。 几乎所有的枚举器和光纤魔术都是用C实现的。

你真的在问纤维和产量是如何实现的吗? 你在寻找什么样的细节?

如果我不在基地,请纠正我。

正如其他海报所指出的那样,我认为它创造了自己的私人“纤维”[在1.9]。 在1.8.7(或1.8.6,如果你使用backports gem)不知何故或其他它做同样的事情(可能是因为1.8中的所有线程都相当于光纤,它只是使用它们?)

因此在1.9和1.8.x中,如果将它们中的几个链接在一起a.each_line.map.each_with_index {}

它实际上是通过每条线流过整个链,就像命令行上的管道一样

http://pragdave.blogs.pragprog.com/pragdave/2007/12/pipelines-using.html

HTH。

我认为这会更准确。 在枚举器上调用每个应该与调用原始迭代器方法相同。 所以我会略微改变原来的解决方案:

 class MyEnumerator include Enumerable def initialize(obj, iterator_method) @f = Fiber.new do @result = obj.send(iterator_method) do |*args| Fiber.yield(*args) end raise StopIteration end end def next(result) @f.resume result end def each result = nil loop do result = yield(self.next(result)) end @result end end