Ruby Fiber指南(一)基础
Ruby Fiber指南(二)参数传递
Ruby Fiber指南(三)过滤器
Ruby Fiber指南(四)迭代器
Ruby Actor指南(五)实现Actor
上一节介绍了利用Fiber实现类unix管道风格的过滤链,这一节将介绍利用Fiber来实现迭代器,
我们可以将循环的迭代器看作生产者-消费者模式的特殊的例子。迭代函数产生值给循环体消费。所以可以使用Fiber来实现迭代器。协程的一个关键特征是它可以不断颠倒调用者与被调用者之间的关系,这样我们毫无顾虑的使用它实现一个迭代器,而不用保存迭代函数返回的状态,也就是说无需在迭代函数中保存状态,状态的保存和恢复交由Fiber自动管理。
这一节的介绍以一个例子贯穿前后,我们将不断演化这个例子,直到得到一个比较优雅的可重用的代码,这个例子就是求数组的全排列。如数组[1,2,3]的全排列包括6种排列:
2 3 1
3 2 1
3 1 2
1 3 2
2 1 3
1 2 3
全排列的递归算法实现很简单,我们用Ruby实现如下:
#全排列的递归实现
def permgen (a, n)
if n == 0 then
printResult(a)
else
n.times do |i|
a[n-1], a[i] = a[i], a[n-1]
permgen(a, n - 1)
a[n-1], a[i] = a[i], a[n-1]
end
end
end
def printResult (a)
puts a.join(" ")
end
permgen([1,2,3,4],4)
算法的思路是这样:将数组中的每一个元素放到最后,依次递归生成所有剩余元素的排列,没完成一个排列就打印出来。很显然,这里有消费者和生产者的关系存在,生产者负责产生排列,消费者负责打印任务,整个程序由消费者驱动,因此用Fiber改写如下:
第一步,将打印任务修改为Fiber#yield,生产者产生一个排列后将结果传递给消费者并让出执行权:
def permgen (a, n)
if n == 0 then
Fiber.yield(a)
……
end
第二步,实现一个迭代器工厂,返回一个匿名的迭代函数,迭代函数请求Fiber产生一个新的排列:
def perm(a)
f=Fiber.new do
permgen(a,a.size)
end
return lambda{ f.resume if f.alive? }
end
这样一来我们就可以利用一个while循环来打印全排列:
it=perm([1,2,3,4])
while a=it.call
printResult(a)
end
注意到,在perm方法中有一个很常见的模式,就是将对Fiber的resume封装在一个匿名函数内,在lua为了支持这种模式还特意提供了一个coroutine.wrap方法来方便编程,在Ruby Fiber中却没有,不过我们可以自己实现下,利用open-class的特性实现起来非常简单:
#为Fiber添加wrap方法
class Fiber
def self.wrap
if block_given?
f=Fiber.new do |*args|
yield *args
end
return lambda{|*args| f.resume(*args) if f.alive? }
end
end
end
Fiber#wrap方法跟new方法一样,创建一个新的Fiber,但是返回的是一个匿名函数,这个匿名函数负责去调用fiber的resume,利用wrap改写perm方法变得更简洁:
def perm(a)
Fiber.wrap{ permgen(a,a.size) }
end
但是还不够,while循环的方式还是不够优雅,每次都需要明确地调用迭代器的call方法,这一点让人挺不舒坦,如果能像for...in那样的泛型循环就好了,我们知道Ruby中的for...in其实是一个语法糖衣,都是转变成调用集合的each方法并传入处理的block,因此,要想实现一个优雅的迭代器,我们做下封装就好了:
class FiberIterator
def initialize
@fiber_wrap=Fiber.wrap do
yield
end
end
def each
while value=@fiber_wrap.call
yield value
end
end
end
那么现在的perm方法变成了创建一个迭代器FiberIterator:
def perm(a)
FiberIterator.new{ permgen(a,a.size) }
end
这样一来我们就可以通过for...in来调用迭代器了
it=perm([1,2,3,4])
for a in it
printResult(a)
end