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

时间:2013-10-29 13:17:14

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

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

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

4 个答案:

答案 0 :(得分:7)

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

本文:'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对象链一次传递一个值。如果我将一系列调用收集或其他Enumerator :: Lazy方法链接在一起,则每个值将从我的一个块传递到下一个,一次一个,每次一个   Enumerable#first都通过在惰性枚举器上调用每个来启动迭代,并在有足够值时通过引发异常来结束迭代。

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

答案 1 :(得分:6)

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, " " 

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

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

答案 2 :(得分:4)

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

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通过提供不可知的Enumerator::Yielder接口使枚举器更容易编写。当您给yielder(yielder.yield numberyielder << number)赋值时,您告诉枚举器“每当有人要求下一个值时(无论是在一个块中,nexteach,或直接传递给另一个调查员),给他们这个。“ yield关键字不会在此处删除它,因为它只是 以便为块产生值。

答案 3 :(得分:0)

我在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 + Private Sub CodeFromYourFollowHyperlinkEvent() ' Put your code in FollowHyperlink here initializeWorksheets Application.ScreenUpdating = False If (ActiveSheet.Name = "Student Viewer") Then . . . End Sub 类创建Ruby 1.8样式Generator

Enumerator