在Ruby中作为无限生成器的枚举数
我正在阅读一个资源,解释如何将枚举数用作生成器,例如:在Ruby中作为无限生成器的枚举数,ruby,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,
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
的目的,它需要什么值,以及该代码如何与程序的其余代码并行执行
执行从顶部开始,可能在块“产生”代码值时暂停
有人能解释一下这些在编译器眼中是如何执行的吗?我想我发现了一些你可能会感兴趣的东西 本文:解释了急切和懒惰评估背后的思想,并解释了它与Enumerale、Generator或Yielder等“框架类”的关系。它主要集中在解释如何实现懒散评估,但仍然非常详细
原始资料来源: Ruby 2.0使用一个名为Enumerator::lazy的对象实现惰性计算。它的特殊之处在于它同时扮演两个角色!它是一个枚举器,还包含一系列可枚举方法。它调用每个函数以从枚举源获取数据,并向枚举的其余部分生成数据。 由于Enumerator::Lazy同时扮演这两个角色,因此可以将它们链接在一起以生成单个枚举。
这是Ruby中延迟计算的关键。数据源中的每个值都会产生到我的块中,然后结果会立即沿着枚举链传递。此枚举不急切–枚举器::Lazy#collect方法不会将值收集到数组中。相反,每一个值都是通过重复的产量沿着Enumerator::Lazy对象链一次传递一个值。如果我将一系列用于collect或其他Enumerator::Lazy方法的调用链接在一起,则每个值都会沿着链从一个块传递到下一个块,每次传递一个
Enumerable#首先两者都通过调用惰性枚举数来启动迭代,并在具有足够值时通过引发异常来结束迭代
在一天结束时,这是惰性评估背后的关键思想:计算链末端的函数或方法开始执行过程,程序流通过函数调用链反向工作,直到它获得所需的数据输入。Ruby使用枚举器::惰性对象链来实现这一点
Yielder只是一段返回值并等待下次调用的代码 这可以通过使用轻松实现。请参见以下创建
simplenumerator
类的示例:
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, " "
我刚刚用simplenumerator.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, " " }
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, " " } }
这样做的缺点是,每次我们想要恢复打印三角形数字时,都需要从头开始。效率低下!我们需要的是一种方法来记住我们在哪里中断了,以便我们以后可以继续。带有proc的变量可以轻松解决问题:
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, " " }
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, " " } }
但这是前进一步,后退两步。我们可以很容易地恢复,但没有封装逻辑(我们可能会意外地更改编号并毁掉一切!)。我们真正想要的是一个对象,我们可以在其中存储状态。这正是枚举器的作用
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中是闭包,循环
会记住调用之间的number
和count
状态。这就是为什么看起来枚举器是并行运行的
现在我们来谈谈元老。请注意,它替换了前面示例中使用proc的blk.call number
<代码>blk.call
起作用,但它不灵活。在Ruby中,您不必总是为枚举数提供块。有时,您只想一次枚举一个步骤或将枚举数链接在一起,在这种情况下,让枚举数简单地将值传递给块是不方便的Enumerator
通过提供不可知的Enumerator::Yielder
接口,使枚举数的编写更加简单。当您给yielder(yielder.yield number
或yielder一个值时,我在Ruby Cookbook中找到了一个简洁的答案:
这展示了如何使用Ruby 2.0+枚举器类创建Ruby 1.8样式的生成器
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
+1,好问题。每个人都知道,当给定一个块时,枚举数可以一个接一个地向该块生成枚举。基本调查会告诉你,新构造的枚举数为自己创建了一个enumerator::Yielder
的实例。然而,enumerator::Yielder
的文档很难说是什么出于种种原因,设计师们将屈服的任务从枚举器
委派给枚举器::Yielder
。我期待着答案。@daremarkovic:IIRC,实际上,它不是并行执行的。下面的机制称为“协同路由”,它是“公正的”回调/委托的巧妙编排。@BorisStitnicky:请看我找到的文章。我发现它很有趣,我认为关于“yielder”的部分解释了你的疑惑。@Quetzalcatl:这是一篇很棒的文章。找到工作很好