假设我们要打印前三个三角数。一个天真的实现是使用一个函数:
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。请注意,它替换blk.call number
了我们使用 proc 的上一个示例。blk.call
工作,但它是不灵活的。在 Ruby 中,您不必总是为枚举器提供块。有时您只想一次枚举一个步骤或将枚举器链接在一起,在这些情况下让您的枚举器简单地将值传递给块是不方便的。通过提供不可知的接口Enumerator
,使枚举器的编写更加简单。Enumerator::Yielder
当你给 yielder (yielder.yield number
或yielder << number
) 一个值时,你是在告诉枚举器“每当有人要求下一个值时(无论是在一个块中,用next
, each
,还是直接传递给另一个枚举器),给他们这个。” yield
关键字根本不会在这里剪掉它,因为它只是为块产生值。