我正在阅读一个资源,解释如何将枚举器用作生成器,例如:
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
这里的目的,它需要什么值,以及这段代码如何与程序代码的其余部分并行执行。
执行从顶部开始,并且当块“产生”我的代码的值时可能暂停。
有人可以在编译器的眼中解释这一切是如何执行的吗?
答案 0 :(得分:7)
我想我找到了一些你可能感兴趣的东西。
本文:'Ruby 2.0 Works Hard So You Can Be Lazy' by Pat Shaughnessy解释了Eager和Lazy评估背后的想法,并解释了它与Enumerale,Generator或Yielder等“框架类”的关系。它主要侧重于解释如何实现LazyEvaluation,但仍然非常详细。
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
会记住调用之间number
和count
的状态。这就是使枚举器看起来像并行运行的原因。
现在我们到了yielder。请注意,它替换了我们使用proc的前一个示例中的blk.call number
。 blk.call
工作,但它不灵活。在Ruby中,您并不总是需要为枚举数提供块。有时您只想一次枚举一个步骤或将枚举器链接在一起,在这些情况下,让您的枚举器只是将值传递给块是不方便的。 Enumerator
通过提供不可知的Enumerator::Yielder
接口使枚举器更容易编写。当您给yielder(yielder.yield number
或yielder << number
)赋值时,您告诉枚举器“每当有人要求下一个值时(无论是在一个块中,next
, each
,或直接传递给另一个调查员),给他们这个。“ yield
关键字不会在此处删除它,因为它只是 以便为块产生值。
答案 3 :(得分:0)
我在Ruby Cookbook中找到了一个很简洁的答案:
这展示了如何使用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