使用生成器块的枚举器行为

时间:2016-10-20 15:10:09

标签: ruby

我在理解Ruby枚举器如何处理生成器块时遇到了一些问题,特别是对于无限序列的生成器。我正在阅读The Well Grounded Rubyist,其中有一个例子:

a = [1, 2, 3, 4, 5]

e = Enumerator.new do |y|
  total = 0
  until a.empty?
    total += a.pop
    y << total
  end
end

e.take(2)
=> [5, 9]
a
=> [1, 2, 3]

我期望它做的是:它从头到尾遍历可枚举,然后返回结果集的前两个元素,将原始数组留空。但是在给它一些思考之后,我意识到在发生器产生无限序列的情况下它不会起作用 - 迭代永远不会结束。

现在,我知道当从外部枚举创建时,Enumerator使用Fibers来停止并恢复执行每次基础Enumerable为每个块产生值(或类似的东西,我只知道一般的想法)。但是,当枚举器是从构造函数和生成器块创建的时候,它是如何工作的?

我试图在底层的本机代码中进行一些挖掘,但很快就在树林中迷失了,因为我的C技能至少可以说是低于标准。根据我的理解,内部each被调用枚举器本身,take_i函数作为块提供。我找不到任何纤维的参考,但是不能深入挖掘。

2 个答案:

答案 0 :(得分:1)

好的,我想我终于明白了。会发生以下情况:

在内部,枚举器上的e.take(n)被Ruby解释为:

result = []
e.each do |item|
  result << item
  break if result.size == 2
end
result

回到我们初始化枚举器的方式,使用构造函数给出的整个块是Enumerator::Generator类的实例的责任,它是与枚举器本身一起创建的。传递给此块的yEnumerator::Yielder类的实例。

在枚举数上调用each时,首先执行生成器代码,当涉及y << total部分时,y传递(产生,就好像来自方法定义,但是不完全)这个total块的值,与each调用一起给出。当这个块完成执行并返回时,控制返回到生成器代码,该代码执行另一个循环,并将新值推送到yielder,这将再次将其生成each块等等。并且当条件在each块变为true,一切都停止,从而使原始数组的一部分保持不变。所以不需要在这里使用Fibers,只是在两个代码块之间来回控制一些很酷的控制。

虽然值得注意的是&#34; each阻止&#34;我所说的不是一个真正的红宝石块,它的C函数take_i,被视为块,被传递给each方法。

您可以在此处阅读有关此内容的更多信息,包括图表和一些额外信息:http://patshaughnessy.net/2013/4/3/ruby-2-0-works-hard-so-you-can-be-lazy

答案 1 :(得分:0)

枚举器更像是一个惰性列表计算器,允许您创建理论上无限的列表,但实际上只需要在需要时计算所需的列表,运行构造函数块中传递的循环只需要它给你的次数你问的是什么因此,在您从e。

请求5件事之前,您的示例不会清空a

Ruby的doc示例中包含具有无限循环的斐波那契序列:https://ruby-doc.org/core-2.2.0/Enumerator.html#method-c-new