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

发布于 2024-08-05 20:12:03 字数 633 浏览 10 评论 0原文

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

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

This question is not about how to use Enumerators in Ruby 1.9.1 but rather I am curious how they work. Here is some code:

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

In the above code I can use e = Bunk.new.each, and then e.next, e.next to get each successive element, but how exactly is it suspending execution and then resuming at the right spot?

I am aware that if the yield in the 0.upto is replaced with Fiber.yield then it's easy to understand, but that is not the case here. It is a plain old yield, so how does it work?

I looked at enumerator.c but it's neigh on incomprehensible for me. Maybe someone could provide an implementation in Ruby, using fibers, not 1.8.6 style continuation-based enumerators, that makes it all clear?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

浅浅淡淡 2024-08-12 20:12:03

这是一个使用 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

以防万一有人对使用控制流异常感到不安:真正的枚举器也在最后引发 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]]

Here's a plain ruby enumerator that uses Fibers and should pretty much behave like the original:

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

And in case anyone is feeling uneasy about using exceptions for control flow: The real Enumerator raises StopIteration at the end, too, so I just emulated the original behaviour.

Usage:

>> 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]]
别再吹冷风 2024-08-12 20:12:03

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

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

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

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

如果我有偏差,请纠正我。

Actually in your e = Bunk.new.each the else clause is not executed initially. Instead the 'if !block_given' clause executes and returns an enumerator object. The enumerator object does keep a fiber object internally. (At least that is what it looks like in enumerator.c)

When you call e.each it is calling a method on an enumerator which uses a fiber internally to keep track of its execution context. This method calls the Bunk.each method using the fibers execution context. The Bunk.each call here does execut the else clause and yields up the value.

I do not know how yield is implemented or how a fiber tracks the execution context. I haven't looked at that code. Almost all of the enumerator and fiber magic is implemented in C.

Are you really asking how fibers and yield are implemented? What level of detail are you looking for?

If I am off base please correct me.

空城仅有旧梦在 2024-08-12 20:12:03

正如其他发帖者所指出的,我相信它创建了自己的私有“光纤”[在 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。

As the other posters noted, I believe it creates its own private "fiber" [in 1.9]. In 1.8.7 (or 1.8.6 if you use the backports gem) somehow or other it does the same thing (perhaps because all threads in 1.8 are the equivalent of fibers, it just uses them?)

Thus in 1.9 and 1.8.x, if you chain several of them together
a.each_line.map.each_with_index { }

It actually flows through that whole chain with each line, kind of like a pipe on the command line

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

HTH.

娜些时光,永不杰束 2024-08-12 20:12:03

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

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

I think this would be more accurate. Calling each on the enumerator should be the same as calling the original iterator method. So I would slightly change the original solution to this:

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文