11

这个问题不是关于如何在 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替换为Fiber.yieldthen 很容易理解,但这里并非如此。这是一个普通的旧的yield,那么它是如何工作的?

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

4

4 回答 4

16

这是一个使用 Fibers 的普通 ruby​​ 枚举器,其行为应该与原始枚举器非常相似:

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

如果有人对使用控制流异常感到不安:真正的 Enumerator 在最后也会引发 StopIteration,所以我只是模拟了原始行为。

用法:

>> enum = MyEnumerator.new([1,2,3,4], :each_with_index)
=> #<MyEnumerator:0x9d184f0 @f=#<Fiber:0x9d184dc>
>> enum.next
=> [1, 0]
>> enum.next
=> [2, 1]
>> enum.to_a
=> [[3, 2], [4, 3]]
于 2009-09-17T09:18:35.463 回答
4

实际上,在您的 e = Bunk.new.each 中,else 子句最初并未执行。相反,'if !block_given' 子句执行并返回一个枚举器对象。枚举器对象确实在内部保留了一个纤程对象。(至少在 enumerator.c 中是这样的)

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

我不知道产量是如何实现的,或者纤程是如何跟踪执行上下文的。我没有看过那个代码。几乎所有的枚举器和光纤魔法都是用 C 实现的。

你真的在问纤维和产量是如何实现的吗?您在寻找什么级别的详细信息?

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

于 2009-09-17T00:14:33.970 回答
1

正如其他海报所指出的,我相信它会创建自己的私有“纤维”[在 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。

于 2009-09-26T14:09:49.427 回答
1

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

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
于 2011-09-13T18:05:31.597 回答