枚举器作为Ruby中的无限生成器

我正在阅读一个资源,解释如何将枚举器用作生成器,例如:

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的目的,它需要什么值,以及这个代码如何与程序代码的其余部分并行执行。

执行从顶部开始,并且可能在块“生成”我的代码的值时暂停。

有人可以解释一下这些在编译器的眼中是如何执行的吗?

我想我找到了一些你可能感兴趣的东西。

这篇文章: “Pat 2.0工作很难,你可以懒惰”,Pat Shaughnessy解释了Eager和Lazy评估背后的想法,并解释了它与Enumerale,Generator或Yielder等“框架类”的关系。 它主要集中在解释如何实现LazyEvaluation,但它仍然非常详细。


原始资料来源: “Pat 2.0努力工作,所以你可以懒惰”,Pat Shaughnessy

Ruby 2.0使用名为Enumerator :: Lazy的对象实现延迟评估。 这个特别之处在于它扮演着两个角色! 它是一个枚举器,还包含一系列Enumerable方法。 它调用每个从枚举源获取数据,并将数据生成到枚举的其余部分。 由于Enumerator :: Lazy扮演两个角色,因此您可以将它们链接在一起以生成单个枚举。

这是Ruby中懒惰评估的关键。 数据源中的每个值都会生成到我的块中,然后结果会立即沿着枚举链传递。 这个枚举并不急 – Enumerator :: Lazy#collect方法不会将值收集到数组中。 而是通过重复的产量,沿着Enumerator :: Lazy对象链一次传递一个值。 如果我将一系列调用收集或其他Enumerator :: Lazy方法链接在一起,则每个值将沿着链从一个块传递到下一个块,一次一个

Enumerable#first都通过在惰性枚举器上调用每个来启动迭代,并在有足够值时引发exception来结束迭代。

在一天结束时,这是延迟评估背后的关键思想:计算链末尾的函数或方法启动执行过程,程序的流程通过函数调用链向后工作,直到它只获取数据它需要的输入。 Ruby使用Enumerator :: Lazy对象链实现了这一点。

Yielder只是一段代码,它返回值并等到下一次调用。

这可以通过使用Ruby Fiber Class轻松实现。 请参阅以下创建SimpleEnumerator类的示例:

 class SimpleEnumerator def initialize &block # creates a new Fiber to be used as an Yielder @yielder = Fiber.new do yield Fiber # call the block code. The same as: block.call Fiber raise StopIteration # raise an error if there is no more calls end end def next # return the value and wait until the next call @yielder.resume end end triangular_numbers = SimpleEnumerator.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, " " 

我刚刚在您的代码中用SimpleEnumerator.new替换了Enumerator.new ,结果是一样的。

有一个“轻量级合作并发”; 使用Ruby文档词,程序员可以安排应该做什么,换句话说,程序员可以暂停和恢复代码块。

假设我们要打印前三个三角形数字。 一个天真的实现是使用一个函数:

 def print_triangular_numbers steps number = 0 count = 1 steps.times do number += count count += 1 print number, " " end end print_triangular_numbers(3) 

这里的缺点是我们将打印逻辑与计数逻辑混合在一起。 如果我们不想打印数字,这没用。 我们可以通过将数字转换为块来改进这一点:

 def triangular_numbers steps number = 0 count = 1 steps.times do number += count count += 1 yield number end end triangular_numbers(3) { |n| print n, " " } 

现在假设我们想要打印一些三角形数字,做一些其他的东西,然后继续打印它们。 再一次,一个天真的解决方案:

 def triangular_numbers steps, start = 0 number = 0 count = 1 (steps + start).times do number += count yield number if count > start count += 1 end end triangular_numbers(4) { |n| print n, " " } # do other stuff triangular_numbers(3, 4) { |n| print n, " " } 

这样做的缺点是,每次我们想要恢复打印三角形数字时,我们都需要从头开始。 低效的! 我们需要的是一种记住我们离开的地方以便以后恢复的方法。 带有proc的变量提供了一个简单的解决方案:

 number = 0 count = 1 triangular_numbers = proc do |&blk| number += count count += 1 blk.call number end 4.times { triangular_numbers.call { |n| print n, " " } } # do other stuff 3.times { triangular_numbers.call { |n| print n, " " } } 

但这是前进一步,后退两步。 我们可以很容易地恢复,但是没有封装逻辑(我们可能会意外地改变number并破坏一切!)。 我们真正想要的是一个可以存储状态的对象 。 这正是Enumerator的用途。

 triangular_numbers = Enumerator.new do |yielder| number = 0 count = 1 loop do number += count count += 1 yielder.yield number end end 4.times { print triangular_numbers.next, " " } # do other stuff 3.times { print triangular_numbers.next, " " } 

由于块是Ruby中的闭包, loop记住调用之间的numbercount状态。 这使得它看起来像是并行运行的枚举器。

现在我们到了yielder。 请注意,它替换了我们使用proc的前一个示例中的blk.call numberblk.call工作,但它不灵活。 在Ruby中,您并不总是需要为枚举器提供块。 有时您只想一次枚举一个步骤或链接枚举器,在这些情况下,让您的枚举器只是将值传递给块是不方便的。 通过提供不可知的Enumerator::Yielder接口, Enumerator使枚举器更容易编写。 当您给yielder( yielder.yield numberyielder << numberyielder.yield number ,您告诉枚举器“每当有人要求下一个值时(无论是在一个块中, nexteach ,还是直接传递给另一位普查员),给他们这个。“ yield关键字根本不会在这里删除它,因为它只是为块产生值。

我在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 =假

这展示了如何使用Ruby 2.0+ Enumerator类创建Ruby 1.8样式Generator

 my_array = ['v1', 'v2'] my_generator = Enumerator.new do |yielder| index = 0 loop do yielder.yield(my_array[index]) index += 1 end end my_generator.next # => 'v1' my_generator.next # => 'v2' my_generator.next # => nil