我正在阅读一个资源,它解释了如何将枚举器用作生成器,例如:
triangular_numbers = Enumerator.new do |yielder|
number = 0
count = 1
loop do
number += count
count += 1
yielder.yield number
end
end
print triangular_numbers.next, " "
print triangular_numbers.next, " "
print triangular_numbers.next, " "
我不明白这里yielder
的用途,它取什么值,以及这段代码是如何与程序的其余代码并行执行的。
执行从顶部开始,可能在块向我的代码“生成”一个值时暂停。
有人能解释一下在编译器的眼中这一切是如何执行的吗?
5条答案
按热度按时间wxclj1h51#
我想我发现了一些你可能会感兴趣的东西。
这篇文章:'Ruby 2.0 Works Hard So You Can Be Lazy' by Pat Shaughnessy解释了Eager和Lazy求值背后的思想,也解释了它如何与“框架类”(如Enumerale、Generator或Yielder)相关联。它主要集中在解释如何实现LazyEvaluation,但仍然相当详细。
原始来源:'Ruby 2.0 Works Hard So You Can Be Lazy' by Pat Shaughnessy
Ruby 2.0使用一个名为Enumerator::Lazy的对象实现了惰性求值。这个对象的特殊之处在于,它同时扮演了两个角色!它是一个枚举器,还包含了一系列的Enumerable方法。它调用每一个方法来从枚举源获取数据,并为枚举的其余部分生成数据。由于Enumerator::Lazy同时扮演了两个角色,因此可以将它们链接在一起以生成一个枚举。
这是Ruby中惰性求值的关键。来自数据源的每个值都被生成到我的块中,然后结果会立即沿着枚举链向下传递。这种枚举并不是即时的--Enumerator::Lazy#collect方法不会将值收集到数组中。相反,每个值都通过重复的生成沿着Enumerator::Lazy对象链一次传递一个。如果我将一系列对collect或其他Enumerator::Lazy方法的调用链接在一起,那么每个值都会沿着链从我的一个块传递到下一个块,一次一个
Enumerable#first通过在惰性枚举器上调用每个枚举器来启动迭代,并在迭代具有足够的值时通过引发异常来结束迭代。
归根结底,这是惰性评估背后的关键思想:位于计算链末端的函数或方法开始执行过程,程序的流程通过函数调用链反向工作,直到获得所需的数据输入。Ruby使用Enumerator::Lazy对象链来实现这一点。
fnvucqvd2#
Yielder
只是一段代码,它返回值并等待下一次调用。这可以通过使用Ruby
Fiber
Class轻松实现。请参见以下创建SimpleEnumerator
类的示例:我只是用
SimpleEnumerator.new
替换了代码中的Enumerator.new
,结果是一样的。存在“轻量级协作并发”;使用Ruby文档中的词语,程序员在其中安排应该做什么,换句话说,程序员可以暂停和恢复代码块。
ut6juiuv3#
假设我们要打印前三个三角形数字,一个简单的实现是使用一个函数:
这里的缺点是我们将打印逻辑和计数逻辑混合在一起。如果我们不想打印数字,这就没有用。我们可以通过将数字 * yield * 到一个块中来改进这一点:
现在,假设我们要打印几个三角形数字,做一些其他的事情,然后继续打印它们。
这样做的缺点是,每次我们想重新打印三角形数字时,都需要从头开始。效率很低!我们需要的是一种方法,它可以记住我们停止的地方,以便以后可以继续。带proc的变量提供了一个简单的解决方案:
但这是前进了一步,后退了两步。我们可以很容易地继续,但没有封装逻辑(我们可能会意外地更改
number
并破坏一切!)。我们 * 真正 * 想要的是一个对象,我们可以在其中存储状态。这正是Enumerator
的用途。因为在Ruby中块是闭包,所以
loop
在调用之间记住number
和count
的状态,这使得枚举器看起来像是并行运行的。现在我们来看看yielder。注意,它取代了前面例子中使用proc的
blk.call number
。blk.call
可以工作,但是不够灵活。在Ruby中,你不一定要提供块枚举器。有时候你只想一次枚举一个步骤,或者把枚举器链接在一起。Enumerator
通过提供不可知的Enumerator::Yielder
接口使枚举器的编写更加简单。当你给予yielder一个值时,(yielder.yield number
或yielder << number
),你告诉枚举器“无论何时有人要求下一个值(无论是块中的值、next
、each
的值,还是直接传递给另一个枚举器的值),请给予他们这个值”。dwthyt8l4#
我在Ruby Cookbook中找到了一个简洁的答案:
https://books.google.com/books?id=xBmkBwAAQBAJ&pg=PT463&lpg=PT463&dq=upgrade+ruby+1.8+generator&source=bl&ots=yyVBoNUhNj&sig=iYXXR_8QqVMasFnS53sbUzGAbTc&hl=en&sa=X&ei=fOM-VZb0BoXSsAWulIGIAw&ved=0CFcQ6AEwBw#v=onepage&q=upgrade%20ruby%201.8%20generator&f=false
这显示了如何使用Ruby 2.0+
Enumerator
类创建Ruby 1.8样式Generator
。iezvtpos5#
Ruby 2.7引入了
Enumerator#produce
,用于从任意块创建一个无限枚举器,这导致了一种非常优雅、非常实用的方式来实现最初的问题: