2009-09-16 26 views
7

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

我看着enumerator.c,但它对我来说是相当不容易理解的。也许有人可以在Ruby中提供一个实现,使用光纤,而不是1.8.6风格的继续式的枚举器,这使得它很清楚?

回答

12

下面是一个使用光纤,应该非常表现得像原来的纯红宝石枚举:

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]] 
4

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

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

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

您真的在问如何实现光纤和产量?你在寻找什么级别的细节?

如果我是关闭基地,请大家指正。

+0

谢谢您的回答。是的,我在这方面要求了很多细节。具体而言,我想知道是否有可能在Ruby中实现它,或者在C中是否有某些秘密行为,这在Ruby中是不可能的。如果可以纯粹用Ruby来实现它,我很乐意看到代码! :) – horseyguy 2009-09-17 01:45:17

1

正如其他海报指出,我相信它会创建自己的私人 “纤维”[1.9]。在1.8.7(或1.8.6如果你使用后端宝石)以某种方式或其他它做同样的事情(也许因为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。

+1

这里是一个很好的详细说明 http://wiki.github.com/rdp/ruby_tutorials_core/enumerator – rogerdpack 2009-11-30 19:47:46

1

我认为这将是更准确。在枚举器上调用每一个应该与调用原始迭代器方法相同。因此,我想稍微改变原来的解决方案是:

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