这个问题不是关于如何在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.next
,e.next
来获取每个连续的元素,但是它究竟是如何暂停执行然后在正确的位置恢复?
我知道如果0.upto
的产量被Fiber.yield
替换,那么很容易理解,但这不是这种情况。这是一个普通的yield
,它是如何工作的?
我看了enumerator.c,但对我来说这是不可理解的。也许有人可以在Ruby中提供一个实现,使用光纤,而不是1.8.6样式的基于延续的枚举器,这一切都清楚了吗?
答案 0 :(得分:14)
这是一个使用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
在有人抱怨流量控制之外的异常之前:真正的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]]
答案 1 :(得分:4)
实际上在你的e = Bunk.new.each中,else子句最初没有被执行。相反,'if!block_given'子句执行并返回枚举器对象。枚举器对象在内部保留光纤对象。 (至少这是它在enumerator.c中的样子)
当你调用e.each时,它会在枚举器上调用一个方法,该方法在内部使用光纤来跟踪其执行上下文。此方法使用光纤执行上下文调用Bunk.each方法。这里的Bunk.each调用确实执行了else子句并产生了值。
我不知道如何实现良率或光纤如何跟踪执行上下文。我没看过那段代码。几乎所有的枚举器和光纤魔术都是用C实现的。
您真的在问纤维和产量是如何实施的吗?你在寻找什么样的细节?
如果我不在基地,请纠正我。
答案 2 :(得分: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。
答案 3 :(得分: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