新聞中心
摘要:作者David Mertz在其文章《可愛的Python:“Python中的函數(shù)式編程”》中的第一部分和第二部分中觸及了函數(shù)式編程的大量基本概念。本文中他將繼續(xù)前面的討論,解釋函數(shù)式編程的其它功能,如currying和Xoltar Toolkit中的其它一些高階函數(shù)。

表達(dá)式綁定
有一位從不滿足于解決部分問題讀者,名叫Richard Davies,提出了一個(gè)問題,問是否可以將所有的綁定全部都轉(zhuǎn)移到一個(gè)單個(gè)的表達(dá)式之中。首先讓我們簡(jiǎn)單看看,我們?yōu)槭裁聪脒@么做,然后再看看由comp.lang.python中的一位朋友提供的一種異常優(yōu)雅地寫表達(dá)式的方式。
讓我們回想一下功能模塊的綁定類。使用該類的特性,我們可以確認(rèn)在一個(gè)給定的范圍塊內(nèi),一個(gè)特定的名字僅僅代表了一個(gè)唯一的事物。
具有重新綁定向?qū)У?Python 函數(shù)式編程(FP)
- >>> from functional import *
- >>> let = Bindings()
- >>> let.car = lambda lst: lst[0]
- >>> let.car = lambda lst: lst[2]
- Traceback (innermost last):
- File "
", line 1, in ? - File "d:\tools\functional.py", line 976, in __setattr__
- raise BindingError, "Binding '%s' cannot be modified." % name
- functional.BindingError: Binding 'car' cannot be modified.
- >>> let.car(range(10))
- 0
綁定類在一個(gè)模塊或者一個(gè)功能定義范圍內(nèi)做這些我們希望的事情,但是沒有辦法在一條表達(dá)式內(nèi)使之工作。然而在ML家族語言(譯者注:ML是一種通用的函數(shù)式編程語言),在一條表達(dá)式內(nèi)創(chuàng)建綁定是很自然的事。
Haskell 命名綁定表達(dá)式
- -- car (x:xs) = x -- *could* create module-level binding
- list_of_list = [[1,2,3],[4,5,6],[7,8,9]]
- -- 'where' clause for expression-level binding
- firsts1 = [car x | x <- list_of_list] where car (x:xs) = x
- -- 'let' clause for expression-level binding
- firsts2 = let car (x:xs) = x in [car x | x <- list_of_list]
- -- more idiomatic higher-order 'map' technique
- firsts3 = map car list_of_list where car (x:xs) = x
- -- Result: firsts1 == firsts2 == firsts3 == [1,4,7]
Greg Ewing 發(fā)現(xiàn)用Python的list概念實(shí)現(xiàn)同樣的效果是有可能的;甚至我們可以用幾乎與Haskell語法一樣干凈的方式做到。
Python 2.0+ 命名綁定表達(dá)式
- >>> list_of_list = [[1,2,3],[4,5,6],[7,8,9]]
- >>> [car_x for x in list_of_list for car_x in
- (x[0],)]
- [1, 4, 7]
在列表解析(list comprehension)中將表達(dá)式放入一個(gè)單項(xiàng)元素(a single-item tuple)中的這個(gè)小技巧,并不能為使用帶有表達(dá)式級(jí)綁定的高階函數(shù)提供任何思路。要使用這樣的高階函數(shù),還是需要使用塊級(jí)(block-level)綁定,就象以下所示:
Python中的使用塊級(jí)綁定的'map()'
- >>> list_of_list = [[1,2,3],[4,5,6],[7,8,9]]
- >>> let = Bindings()
- >>> let.car = lambda l: l[0]
- >>> map(let.car,list_of_list)
- [1, 4, 7]
這樣真不錯(cuò),但如果我們想使用函數(shù)map(),那么其中的綁定范圍可能會(huì)比我們想要的更寬一些。然而,我們可以做到的,哄騙列表解析讓它替我們做名字綁定,即使其中的列表并不是我們最終想要得到的列表的情況下也沒問題:
從Python的列表解析中“走下舞臺(tái)”
- # Compare Haskell expression:
- # result = func car_car
- # where
- # car (x:xs) = x
- # car_car = car (car list_of_list)
- # func x = x + x^2
- >>> [func for x in list_of_list
- ...
- for car in (x[0],)
- ...
- for func in (car+car**2,)][0]
- 2
我們對(duì)list_of_list列表中第一個(gè)元素的第一個(gè)元素進(jìn)行了一次算數(shù)運(yùn)算,而且期間還對(duì)該算術(shù)運(yùn)算進(jìn)行了命名(但其作用域僅僅是在表達(dá)式的范圍內(nèi))。作為一種“優(yōu)化”,我們可以不用費(fèi)心創(chuàng)建多于一個(gè)元素的列表就能開始運(yùn)算了,因?yàn)槲覀兘Y(jié)尾處用的索引為0,所以我們僅僅選擇的是第一個(gè)元素。:
從列表解析中高效地走下舞臺(tái)
- >>> [func for x in list_of_list[:1]
- ... for car in (x[0],)
- ... for func in (car+car**2,)][0]
- 2
高階函數(shù):currying
Python內(nèi)建的三個(gè)最常用的高階函數(shù)是:map()、reduce()和filter()。這三個(gè)函數(shù)所做的事情 —— 以及謂之為“高階”(higher-order)的原因 —— 是接受其它函數(shù)作為它們的(部分)參數(shù)。還有別的一些不屬于內(nèi)置的高階函數(shù),還會(huì)返回函數(shù)對(duì)象。
藉由函數(shù)對(duì)象在Python中具有首要地位, Python一直都有能讓其使用者構(gòu)造自己的高階函數(shù)的能力。舉個(gè)如下所示的小例子:
Python中一個(gè)簡(jiǎn)單函數(shù)工廠(function factory)
- >>> def
- foo_factory():
- ...
- def
- foo():
- ...
- "Foo function from factory"
- ...
- return foo
- ...
- >>> f = foo_factory()
- >>> f()
- Foo function from factory
本系列文章的第二部分我討論過的Xoltar Toolkit中,有一組非常好用的高階函數(shù)。Xoltar的functional模塊中提供的絕大多數(shù)高階函數(shù)都是在其它各種不同的傳統(tǒng)型函數(shù)式編程語言中發(fā)展出來的高階函數(shù),其有用性已經(jīng)過多年的實(shí)踐驗(yàn)證。
可能其中最著名、最有用和最重要的高階函數(shù)要數(shù)curry()了。函數(shù)curry()的名字取自于邏輯學(xué)家Haskell Curry,前文提及的一種編程語言也是用他姓名當(dāng)中的名字部分命名的。"currying"背后隱含的意思是,(幾乎)每一個(gè)函數(shù)都可以視為只帶一個(gè)參數(shù)的部分函數(shù)(partial function)。要使currying能夠用起來所需要做的就是讓函數(shù)本身的返回值也是個(gè)函數(shù),只不過所返回的函數(shù)“縮小了范圍”或者是“更加接近完整的函數(shù)”。這和我在第二部分中提到的閉包特別相似 —— 對(duì)經(jīng)過curry后的返回的后繼函數(shù)進(jìn)行調(diào)用時(shí)一步一步“填入”最后計(jì)算所需的更多數(shù)據(jù)(附加到一個(gè)過程(procedure)之上的數(shù)據(jù))
現(xiàn)在讓我們先用Haskell中一個(gè)很簡(jiǎn)單例子對(duì)curry進(jìn)行講解,然后在Python中使用functional模塊重復(fù)展示一下這個(gè)簡(jiǎn)單的例子:
在Haskell計(jì)算中使用Curry
- computation a b c d = (a + b^2+ c^3 + d^4)
- check = 1 + 2^2 + 3^3 + 5^4
- fillOne = computation 1
- -- specify "a"
- fillTwo = fillOne 2
- -- specify "b"
- fillThree = fillTwo 3
- -- specify "c"
- answer = fillThree 5
- -- specify "d"
- -- Result: check == answer == 657
現(xiàn)在使用Python:
在Python計(jì)算中使用Curry
- >>> from functional import curry
- >>> computation = lambda a,b,c,d: (a + b**2 + c**3 + d**4)
- >>> computation(1,2,3,5)
- 657
- >>> fillZero = curry(computation)
- >>> fillOne = fillZero(1)
- # specify "a"
- >>> fillTwo = fillOne(2)
- # specify "b"
- >>> fillThree = fillTwo(3)
- # specify "c"
- >>> answer = fillThree(5)
- # specify "d"
- >>> answer
- 657
第二部分中提到過的一個(gè)簡(jiǎn)單的計(jì)稅程序的例子,當(dāng)時(shí)用的是閉包(這次使用curry()),可以用來進(jìn)一步做個(gè)對(duì)比:
Python中curry后的計(jì)稅程序
- from functional import *
- taxcalc = lambda income,rate,deduct: (income-(deduct))*rate
- taxCurry = curry(taxcalc)
- taxCurry = taxCurry(50000)
- taxCurry = taxCurry(0.30)
- taxCurry = taxCurry(10000)
- print "Curried taxes due =",taxCurry
- print "Curried expression taxes due =", \
- curry(taxcalc)(50000)(0.30)(10000)
和使用閉包不同,我們需要以特定的順序(從左到右)對(duì)參數(shù)進(jìn)行curry處理。當(dāng)要注意的是,functional模塊中還包含一個(gè)rcurry()類,能夠以相反的方向進(jìn)行curry處理(從右到左)。
從一個(gè)層面講,其中的第二個(gè)print語句同簡(jiǎn)單的同普通的taxcalc(50000,0.30,10000)函數(shù)調(diào)用相比只是個(gè)微小的拼寫方面的變化。但從另一個(gè)不同的層面講,它清晰地一個(gè)概念,那就是,每個(gè)函數(shù)都可以變換成僅僅帶有一個(gè)參數(shù)的函數(shù),這對(duì)于剛剛接觸這個(gè)概念的人來講,會(huì)有一種特別驚奇的感覺。
其它高階函數(shù)
除了上述的curry功能,functional模塊簡(jiǎn)直就是一個(gè)很有意思的高階函數(shù)萬能口袋。此外,無論用還是不用functional模塊,編寫你自己的高階函數(shù)真的并不難。至少functional模塊中的那些高階函數(shù)為你提供了一些很值一看的思路。
它里面的其它高階函數(shù)在很大程度上感覺有點(diǎn)象是“增強(qiáng)”版本的標(biāo)準(zhǔn)高階函數(shù)map()、filter()和reduce()。這些函數(shù)的工作模式通常大致如此:將一個(gè)或多個(gè)函數(shù)以及一些列表作為參數(shù)接收進(jìn)來,然后對(duì)這些列表參數(shù)運(yùn)行它前面所接收到的函數(shù)。在這種工作模式方面,有非常大量很有意思也很有用的擺弄方法。還有一種模式是:拿到一組函數(shù)后,將這組函數(shù)的功能組合起來創(chuàng)建一個(gè)新函數(shù)。這種模式同樣也有大量的變化形式。下面讓我們看看functional模塊里到底還有哪些其它的高階函數(shù)。
sequential()和also()這兩個(gè)函數(shù)都是在一系列成分函數(shù)(component function)的基礎(chǔ)上創(chuàng)建一個(gè)新函數(shù)。然后這些成分函數(shù)可以通過使用相同的參數(shù)進(jìn)行調(diào)用。兩者的主要區(qū)別就在于,sequential()需要一個(gè)單個(gè)的函數(shù)列表作為參數(shù),而also()接受的是一系列的多個(gè)參數(shù)。在多數(shù)情況下,對(duì)于函數(shù)的副作用而已這些會(huì)很有用,只是sequential()可以讓你隨意選擇將哪個(gè)函數(shù)的返回值作為組合起來后的新函數(shù)的返回值。
順序調(diào)用一系列函數(shù)(使用相同的參數(shù))
- >>> def a(x):
- ... print x,
- ... return "a"
- ...
- >>> def b(x):
- ... print x*2,
- ... return "b"
- ...
- >>> def c(x):
- ... print x*3,
- ... return "c"
- ...
- >>> r = also(a,b,c)
- >>> r
- >>> r(5)
- 5 10 15
- 'a'
- >>> sequential([a,b,c],main=c)('x')
- x xx xxx
- 'c'
isjoin()和conjoin()這兩個(gè)函數(shù)同equential()和also()在創(chuàng)建新函數(shù)并對(duì)參數(shù)進(jìn)行多個(gè)成分函數(shù)的調(diào)用方面非常相似。只是disjoin()函數(shù)用來查詢成分函數(shù)中是否有一個(gè)函數(shù)的返回值(針對(duì)給定的參數(shù))為真;conjoin()函數(shù)用來查詢是否所有的成分函數(shù)的返回值都為真。在這些函數(shù)中只要條件允許就會(huì)使用邏輯短路,因此disjoin()函數(shù)可能不會(huì)出現(xiàn)某些副作用。joinfuncs()i同also()類似,但它返回的是由所有成分函數(shù)的返回值組成的一個(gè)元組(tuple),而不是選中的某個(gè)主函數(shù)。
前文所述的幾個(gè)函數(shù)讓你可以使用相同的參數(shù)對(duì)一系列函數(shù)進(jìn)行調(diào)用,而any()、all()和 none_of()這三個(gè)讓你可以使用一個(gè)參數(shù)列表對(duì)同一個(gè)函數(shù)進(jìn)行多次調(diào)用。在大的結(jié)構(gòu)方面,這些函數(shù)同內(nèi)置的map()、reduce()和filter()有點(diǎn)象。 但funtional模塊中的這三個(gè)高階函數(shù)中都是對(duì)一組返回值進(jìn)行布爾(boolean)運(yùn)算得到其返回值的。例如:
對(duì)一系列返回值的真、假情況進(jìn)行判斷
- >>> from functional import *
- >>> isEven = lambda n: (n%2 == 0)
- >>> any([1,3,5,8], isEven)
- 1
- >>> any([1,3,5,7], isEven)
- 0
- >>> none_of([1,3,5,7], isEven)
- 1
- >>> all([2,4,6,8], isEven)
- 1
- >>> all([2,4,6,7], isEven)
- 0
有點(diǎn)數(shù)學(xué)基礎(chǔ)的人會(huì)對(duì)這個(gè)高階函數(shù)非常感興趣:iscompose(). 將多個(gè)函數(shù)進(jìn)行合成(compostion)指的是,將一個(gè)函數(shù)的返回值同下個(gè)函數(shù)的輸入“鏈接到一起”。對(duì)多個(gè)函數(shù)進(jìn)行合成的程序員需要負(fù)責(zé)保證函數(shù)間的輸入和輸出是相互匹配的,不過這個(gè)條件無論是程序員在何時(shí)想使用返回值時(shí)都是需要滿足的。舉個(gè)簡(jiǎn)單的例子和闡明這一點(diǎn):
創(chuàng)建合成函數(shù)
- >>> def minus7(n): return n-7
- ...
- >>> def times3(n): return n*3
- ...
- >>> minus7(10)
- 3
- >>> minustimes = compose(times3,minus7)
- >>> minustimes(10)
- 9
- >>> times3(minus7(10))
- 9
- >>> timesminus = compose(minus7,times3)
- >>> timesminus(10)
- 23
- >>> minus7(times3(10))
- 23
后會(huì)有期
衷心希望我對(duì)高階函數(shù)的思考能夠引起讀者的興趣。無論如何,請(qǐng)動(dòng)手試一試。試著編寫一些你自己的高階函數(shù);一些可能很有用,很強(qiáng)大。告訴我它如何運(yùn)行;或許這個(gè)系列之后的章節(jié)會(huì)討論讀者不斷提供的新觀點(diǎn),新想法。
網(wǎng)頁題目:可愛的Python函數(shù)式編程(三)
本文URL:http://fisionsoft.com.cn/article/dhhcgsj.html


咨詢
建站咨詢
