函數式編程之pipeline——很酷有沒有


Pipeline

pipeline 管道借鑒於Unix Shell的管道操作——把若干個命令串起來,前面命令的輸出成為后面命令的輸入,如此完成一個流式計算。(注:管道絕對是一個偉大的發明,他的設哲學就是KISS – 讓每個功能就做一件事,並把這件事做到極致,軟件或程序的拼裝會變得更為簡單和直觀。這個設計理念影響非常深遠,包括今天的Web Service,雲計算,以及大數據的流式計算等等)

比如,我們如下的shell命令:

1
ps  auwwx |  awk  '{print $2}'  sort  -n |  xargs  echo

如果我們抽象成函數式的語言,就像下面這樣:

1
xargs(  echo, sort(n, awk( 'print $2' , ps(auwwx)))  )

也可以類似下面這個樣子:

1
pids  =  for_each(result, [ps_auwwx, awk_p2, sort_n, xargs_echo])

好了,讓我們來看看函數式編程的Pipeline怎么玩?

我們先來看一個如下的程序,這個程序的process()有三個步驟:

1)找出偶數。
2)乘以3
3)轉成字符串返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def  process(num):
     # filter out non-evens
     if  num  %  2  ! =  0 :
         return
     num  =  num  *  3
     num  =  'The Number: %s'  %  num
     return  num
 
nums  =  [ 1 2 3 4 5 6 7 8 9 10 ]
 
for  num  in  nums:
     print  process(num)
 
# 輸出:
# None
# The Number: 6
# None
# The Number: 12
# None
# The Number: 18
# None
# The Number: 24
# None
# The Number: 30

我們可以看到,輸出的並不夠完美,另外,代碼閱讀上如果沒有注釋,你也會比較暈。下面,我們來看看函數式的pipeline(第一種方式)應該怎么寫?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def  even_filter(nums):
     for  num  in  nums:
         if  num  %  2  = =  0 :
             yield  num
def  multiply_by_three(nums):
     for  num  in  nums:
         yield  num  *  3
def  convert_to_string(nums):
     for  num  in  nums:
         yield  'The Number: %s'  %  num
 
nums  =  [ 1 2 3 4 5 6 7 8 9 10 ]
pipeline  =  convert_to_string(multiply_by_three(even_filter(nums)))
for  num  in  pipeline:
     print  num
# 輸出:
# The Number: 6
# The Number: 12
# The Number: 18
# The Number: 24
# The Number: 30

我們動用了Python的關鍵字 yield,這個關鍵字主要是返回一個Generator,yield 是一個類似 return 的關鍵字,只是這個函數返回的是個Generator-生成器。所謂生成器的意思是,yield返回的是一個可迭代的對象,並沒有真正的執行函數。也就是說,只有其返回的迭代對象被真正迭代時,yield函數才會正真的運行,運行到yield語句時就會停住,然后等下一次的迭代。(這個是個比較詭異的關鍵字)這就是lazy evluation。

好了,根據前面的原則——“使用Map & Reduce,不要使用循環”,那我們用比較純朴的Map & Reduce吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def  even_filter(nums):
     return  filter ( lambda  x: x % 2 = = 0 , nums)
 
def  multiply_by_three(nums):
     return  map ( lambda  x: x * 3 , nums)
 
def  convert_to_string(nums):
     return  map ( lambda  x:  'The Number: %s'  %  x,  nums)
 
nums  =  [ 1 2 3 4 5 6 7 8 9 10 ]
pipeline  =  convert_to_string(
                multiply_by_three(
                    even_filter(nums)
                )
             )
for  num  in  pipeline:
     print  num

但是他們的代碼需要嵌套使用函數,這個有點不爽,如果我們能像下面這個樣子就好了(第二種方式)。

1
2
3
pipeline_func(nums, [even_filter,
                      multiply_by_three,
                      convert_to_string])

那么,pipeline_func 實現如下:

1
2
3
4
def  pipeline_func(data, fns):
     return  reduce ( lambda  a, x: x(a),
                   fns,
                   data)

好了,在讀過這么多的程序后,你可以回頭看一下這篇文章的開頭對函數式編程的描述,可能你就更有感覺了。

最后,我希望這篇淺顯易懂的文章能讓你感受到函數式編程的思想,就像OO編程,泛型編程,過程式編程一樣,我們不用太糾結是不是我們的程序就是OO,就是functional的,我們重要的品味其中的味道

參考

補充:評論中redraiment這個評論大家也可以讀一讀。

感謝謝網友S142857 提供的shell風格的python pipeline:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class  Pipe( object ):
     def  __init__( self , func):
         self .func  =  func
 
     def  __ror__( self , other):
         def  generator():
             for  obj  in  other:
                 if  obj  is  not  None :
                     yield  self .func(obj)
         return  generator()
 
@Pipe
def  even_filter(num):
     return  num  if  num  %  2  = =  0  else  None
 
@Pipe
def  multiply_by_three(num):
     return  num * 3
 
@Pipe
def  convert_to_string(num):
     return  'The Number: %s'  %  num
 
@Pipe
def  echo(item):
     print  item
     return  item
 
def  force(sqs):
     for  item  in  sqs:  pass
 
nums  =  [ 1 2 3 4 5 6 7 8 9 10 ]
 
force(nums | even_filter | multiply_by_three | convert_to_string | echo)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM