【问题标题】:How do Enumerators work in Ruby 1.9.1?枚举器如何在 Ruby 1.9.1 中工作?
【发布时间】:2010-11-28 23:49:57
【问题描述】:

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

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

【问题讨论】:

    标签: ruby ruby-1.9 fiber enumerators


    【解决方案1】:

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

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

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

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

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

    【讨论】:

    • 感谢您的回答。是的,我要求对此提供很多细节。具体来说,我想知道是否有可能在 Ruby 中实现这一切,或者在 C 中是否有一些在 Ruby 中无法实现的鬼鬼祟祟的东西。如果可以纯粹用 Ruby 实现它,我很乐意看到代码! :)
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      【解决方案4】:

      我认为这会更准确。在枚举器上调用 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
      

      【讨论】:

        猜你喜欢
        • 2017-10-07
        • 2018-02-23
        • 1970-01-01
        • 2012-11-01
        • 1970-01-01
        • 2012-11-07
        • 1970-01-01
        • 1970-01-01
        • 2010-09-09
        相关资源
        最近更新 更多