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的,我們重要的品味其中的味道。
參考
- Wikipedia: Functional Programming
- truly understanding the difference between procedural and functional
- A practical introduction to functional programming
- What is the difference between procedural programming and functional programming?
- Can someone give me examples of functional programming vs imperative/procedural programming?
- OOP vs Functional Programming vs Procedural
- Python – Functional Programming HOWTO
補充:評論中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)
|