Ruby Fiber指南(一)基礎(chǔ)
Ruby Fiber指南(二)參數(shù)傳遞
Ruby Fiber指南(三)過濾器
Ruby Fiber指南(四)迭代器
Ruby Actor指南(五)實現(xiàn)Actor
上一節(jié)介紹了利用Fiber實現(xiàn)類unix管道風(fēng)格的過濾鏈,這一節(jié)將介紹利用Fiber來實現(xiàn)迭代器,
我們可以將循環(huán)的迭代器看作生產(chǎn)者-消費者模式的特殊的例子。迭代函數(shù)產(chǎn)生值給循環(huán)體消費。所以可以使用Fiber來實現(xiàn)迭代器。協(xié)程的一個關(guān)鍵特征是它可以不斷顛倒調(diào)用者與被調(diào)用者之間的關(guān)系,這樣我們毫無顧慮的使用它實現(xiàn)一個迭代器,而不用保存迭代函數(shù)返回的狀態(tài),也就是說無需在迭代函數(shù)中保存狀態(tài),狀態(tài)的保存和恢復(fù)交由Fiber自動管理。
這一節(jié)的介紹以一個例子貫穿前后,我們將不斷演化這個例子,直到得到一個比較優(yōu)雅的可重用的代碼,這個例子就是求數(shù)組的全排列。如數(shù)組[1,2,3]的全排列包括6種排列:
2 3 1
3 2 1
3 1 2
1 3 2
2 1 3
1 2 3
全排列的遞歸算法實現(xiàn)很簡單,我們用Ruby實現(xiàn)如下:
#全排列的遞歸實現(xiàn)
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)
算法的思路是這樣:將數(shù)組中的每一個元素放到最后,依次遞歸生成所有剩余元素的排列,沒完成一個排列就打印出來。很顯然,這里有消費者和生產(chǎn)者的關(guān)系存在,生產(chǎn)者負(fù)責(zé)產(chǎn)生排列,消費者負(fù)責(zé)打印任務(wù),整個程序由消費者驅(qū)動,因此用Fiber改寫如下:
第一步,將打印任務(wù)修改為Fiber#yield,生產(chǎn)者產(chǎn)生一個排列后將結(jié)果傳遞給消費者并讓出執(zhí)行權(quán):
def permgen (a, n)
if n == 0 then
Fiber.yield(a)
……
end
第二步,實現(xiàn)一個迭代器工廠,返回一個匿名的迭代函數(shù),迭代函數(shù)請求Fiber產(chǎn)生一個新的排列:
def perm(a)
f=Fiber.new do
permgen(a,a.size)
end
return lambda{ f.resume if f.alive? }
end
這樣一來我們就可以利用一個while循環(huán)來打印全排列:
it=perm([1,2,3,4])
while a=it.call
printResult(a)
end
注意到,在perm方法中有一個很常見的模式,就是將對Fiber的resume封裝在一個匿名函數(shù)內(nèi),在lua為了支持這種模式還特意提供了一個coroutine.wrap方法來方便編程,在Ruby Fiber中卻沒有,不過我們可以自己實現(xiàn)下,利用open-class的特性實現(xiàn)起來非常簡單:
#為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方法一樣,創(chuàng)建一個新的Fiber,但是返回的是一個匿名函數(shù),這個匿名函數(shù)負(fù)責(zé)去調(diào)用fiber的resume,利用wrap改寫perm方法變得更簡潔:
def perm(a)
Fiber.wrap{ permgen(a,a.size) }
end
但是還不夠,while循環(huán)的方式還是不夠優(yōu)雅,每次都需要明確地調(diào)用迭代器的call方法,這一點讓人挺不舒坦,如果能像for...in那樣的泛型循環(huán)就好了,我們知道Ruby中的for...in其實是一個語法糖衣,都是轉(zhuǎn)變成調(diào)用集合的each方法并傳入處理的block,因此,要想實現(xiàn)一個優(yōu)雅的迭代器,我們做下封裝就好了:
class FiberIterator
def initialize
@fiber_wrap=Fiber.wrap do
yield
end
end
def each
while value=@fiber_wrap.call
yield value
end
end
end
那么現(xiàn)在的perm方法變成了創(chuàng)建一個迭代器FiberIterator:
def perm(a)
FiberIterator.new{ permgen(a,a.size) }
end
這樣一來我們就可以通過for...in來調(diào)用迭代器了
it=perm([1,2,3,4])
for a in it
printResult(a)
end