學(xué)會像一個函數(shù)式程序員那樣思考_第1頁
學(xué)會像一個函數(shù)式程序員那樣思考_第2頁
已閱讀5頁,還剩4頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

1、 學(xué)會像一個函數(shù)式程序員那樣思考在開始進入正題之前,我們先來做一個比喻。假設(shè)你是一個伐木工人,你擁有一把這個森林里最好的斧子,而它使也你成為了當?shù)刈钣猩a(chǎn)力的伐木工人。 某一天,有人向你展示并稱贊了一個新的伐木工具-電鋸。由于銷售人員是一個非常能推銷的人,所以你買了一把電鋸回來,盡管你并不知道如何去用。于是你嘗試像以前砍樹那樣的來回擺動去鋸樹。并且你很快得出了一個結(jié)論這個新式的電鋸毫無用處,于是你又重新拿起斧子去伐木。一直到有人過來并給你演示了如何去運轉(zhuǎn)電鋸,你才明白這里的不同。你可能聯(lián)想到了用函數(shù)式編程來代替故事中的電鋸。但是問題在于函數(shù)式編程是一種全新的編程模式,而不是一門新的語言,語法只

2、是一個細節(jié)問題。而最不同的地方是要如何以不同的方式去思考。而我作為一名“電鋸演示者”和一個函數(shù)式程序員來到了這里。歡迎來到函數(shù)式思維專欄。這個系列將探索函數(shù)式編程的話題,但是并不僅僅局限在函數(shù)式編程語言有關(guān)的內(nèi)容上。正如我描繪的那樣,以函數(shù)式的方法來寫代碼涉及到了設(shè)計,權(quán)衡,代碼重用和其他一系列的觀點。我會嘗試著以Java(或是類Java語言)的方式盡可能多的展示函數(shù)式編程的概念, 進而演示一些其他語言的能力-那些Java不具有的能力。當然我不會直接切入的非常深,然后討論一些時髦的事物。取而代之的是,我會逐漸演示一種新的思考問題的方式(或許你已經(jīng)在某些地方用了,但還沒有意識到)。在接下來的兩部

3、分里,你可以把它當作是有關(guān)于函數(shù)式編程話題的一個旅行。其中的某些概念將會有大量的細節(jié),在這個系列中我會用更多的情景和細節(jié)去描述。在旅程開始前,我將帶你看一下一個相同問題的兩個不同實現(xiàn),一個用傳統(tǒng)的方式來寫,另一個使用更多的函數(shù)式方式。數(shù)字歸類談?wù)搩煞N不同的編程模式,你必須用代碼來做比較。第一個例子是我另一本書The Productive Programmer和測試驅(qū)動設(shè)計1,2兩篇文章中的一個變體。我選取了少量的代碼,因為在這兩篇文章里已經(jīng)深入的分析了這段代碼。這些文章對這個設(shè)計所做的稱贊并沒有錯,但我想在這里進一步提供一個不同的設(shè)計意圖。問題的需求是這樣的:假設(shè)給定任意一個正整數(shù)都大于1,你

4、必須按照完美的,過剩的和不足的進行歸類。一個完美數(shù)正好是它所有整除因子的總和。同樣地,一個過剩數(shù)的所有整除因子總和大于該數(shù),而一個不足數(shù)的所有整除因子總和小于該數(shù)??焖贁?shù)字歸類器列表1中的類(NumberClassifier)滿足所有這些需求:publicclassClassifier6privateSet_factors;privateint_number;publicClassifier6(intnumber)if(number1)thrownewInvalidNumberException(Cantclassifynegativenumbers);_number=number;_fact

5、ors=newHashSet();_factors.add(1);_factors.add(_number);privatebooleanisFactor(intfactor)return_number%factor=0;publicSetgetFactors()return_factors;privatevoidcalculateFactors()for(inti=1;i_number;publicbooleanisDeficient()returnsumOfFactors()-_number_number;publicstaticbooleanisPerfect(intnumber)ret

6、urnnewClassifier6(number).isPerfect();這段代碼有幾處地方需要關(guān)注一下:它擁有大范圍的測試(有一部分我是為了討論測試驅(qū)動開發(fā)而寫的)注:這條所說的測試位于作者另一篇文章中。這個類由大量的緊耦合方法組成,在它的構(gòu)造函數(shù)中擁有測試驅(qū)動開發(fā)的邊際效應(yīng)。在calculateFactors()方法里內(nèi)嵌了性能優(yōu)化算法。這個類的主體是由采集因子組成,因此我可以在之后對它們進行求和并進行最終的歸類。整除因子總是以成對的形式被獲取。例如,如果這個數(shù)是16,當我采集的因子為2時,我就能得到另一個因子為8,因為8x2=16。如果我獲得的因子是成對的,那么我只需要去檢查那些有平方

7、根的數(shù),這就是calculateFactors()方法所做的事情。更多的功能歸類使用相同的測試開發(fā)技術(shù),我創(chuàng)建了一個修改后的版本。列表2,更豐富的功能數(shù)字歸類器publicclassNumberClassifierstaticpublicbooleanisFactor(intnumber,intpotential_factor)returnnumber%potential_factor=0;staticpublicSetfactors(intnumber)HashSetfactors=newHashSet();for(inti=1;i=sqrt(number);i+)if(isFactor(n

8、umber,i)factors.add(i);factors.add(number/i);returnfactors;staticpublicintsum(Setfactors)Iteratorit=factors.iterator();intsum=0;while(it.hasNext()sum+=(Integer)it.next();returnsum;staticpublicbooleanisPerfect(intnumber)returnsum(factors(number)-number=number;staticpublicbooleanisAbundant(intnumber)r

9、eturnsum(factors(number)-numbernumber;staticpublicbooleanisDeficient(intnumber)returnsum(factors(number)-numbernumber;這兩個版本的類盡管差別細微但是很重要。最主要的區(qū)別是例2的版本缺少了狀態(tài)共享。消除狀態(tài)共享在函數(shù)式編程中是比較受歡迎的一種抽象手法。作為跨方法共享狀態(tài)的替代方案,我采用直接調(diào)用的方式來消除狀態(tài)共享。從設(shè)計的角度來說,它讓factors()方法變的更長,但是它也防止了factors字段暴漏到方法之外。注意,例2是完全由靜態(tài)方法組成的。在方法間不存在知識共享的問題,

10、因此我可以在更少函數(shù)范圍上做封裝。一旦你給它們輸入?yún)?shù)和期待值,這些方法都會工作的很好(這個是一個純函數(shù)例子,這個概念我在將來會進一步探索它)。函數(shù)函數(shù)式編程屬于一個寬泛的計算機科學(xué)范疇,它已經(jīng)受到了極大的關(guān)注。有新的基于JVM上開發(fā)的函數(shù)式語言(如scala和clojure)和框架(如Functional Java和Akka),它們都聲稱能夠帶來更少的缺陷,更高的生產(chǎn)力,更易讀,更賺錢等等。相比駐足門外的去解決函數(shù)式編程這一大話題,我更愿意將注意力放在一些概念以及這些概念衍生出來的話題上。函數(shù)式編程的核心就是函數(shù), 正如在面向?qū)ο笳Z言里面類是主要的抽象那樣。函數(shù)形成了處理過程的基礎(chǔ),同時它具

11、有其他傳統(tǒng)語言沒有的一系列特性。高階函數(shù)高階函數(shù)可以將其他函數(shù)作為參數(shù)或者作為返回結(jié)果。這在Java語言中是無法想像的。最接近的方案是你使用一個類(通常是匿名類)作為執(zhí)行方法的“持有者”。Java沒有獨立的函數(shù)(或方法),因此它們不能作為返回值或參數(shù)出現(xiàn)。這個能力對函數(shù)式語言來說很重要的原因有兩點:第一,擁有高階函數(shù)就意為著你可以在如何連結(jié)語言元素上作出一個假設(shè)。例如,你可以構(gòu)建一個機制來消除一個類繼承體系上的一大堆方法,通過遍歷列表并對每一個元素應(yīng)用一個(或多個)高階函數(shù)來實現(xiàn)。(我將展示一個簡短的例子給你)第二,通過將函數(shù)作為返回值,你有機會去創(chuàng)建一個高動態(tài),適應(yīng)性的系統(tǒng)。通過使用高階函數(shù)

12、我們就可以使問題服從于方案,但高階函數(shù)對函數(shù)式語言來說并不是唯一的。因此,當你在使用函數(shù)式思維的時候, 你解決問題思路就會不一樣。考慮一下列表3中的例子,一個保護數(shù)據(jù)訪問的方法:publicvoidaddOrderFrom(ShoppingCartcart,StringuserName,Orderorder)throwsExceptionsetupDataInfrastructure();tryadd(order,userKeyBasedOn(userName);addLineItemsFrom(cart,order.getOrderKey();completeTransaction();ca

13、tch(Exceptioncondition)rollbackTransaction();throwcondition;finallycleanUp();列表3中的代碼執(zhí)行初始化等具體任務(wù),如果所有操作都成功就完成事務(wù),反之回滾,并在最后清理掉資源。很明顯,代碼有一部分可以被重用,并且我們在面向?qū)ο笳Z言中也常常創(chuàng)建這樣的結(jié)構(gòu)。在這個例子中,我組合使用了兩個“四人團”的設(shè)計模式:模版方法和命令模式。模版方法建議我應(yīng)該移動一些通用的模版代碼到繼承體系中,并推遲算法細節(jié)到子類。命令行模式提供了一個方法以眾所周知的執(zhí)行語義來封裝行為到類,列表4就是列表3代碼應(yīng)用這兩個模式之后的樣子:列表4 重構(gòu)順序后

14、的代碼publicvoidwrapInTransaction(Commandc)throwsExceptionsetupDataInfrastructure();tryc.execute();completeTransaction();catch(Exceptioncondition)rollbackTransaction();throwcondition;finallycleanUp();publicvoidaddOrderFrom(finalShoppingCartcart,finalStringuserName,finalOrderorder)throwsExceptionwrapInT

15、ransaction(newCommand()publicvoidexecute()add(order,userKeyBasedOn(userName);addLineItemsFrom(cart,order.getOrderKey(););在列表4中,我提取了一部分通用的代碼到wrapInTransaction()(這個樣式你可能認識-這是最簡單的Spring事務(wù)模版的版本)方法中,傳遞一個命令對象作為工作單元。addOrderFrom()方法包含了一個匿名內(nèi)部類的創(chuàng)建,這個類以命令模式封裝了兩個工作單元。封裝行為純粹是Java的設(shè)計產(chǎn)物,我需要用到一個不包含任何形式的,獨立行為的命令類。J

16、ava中所有的行為都必須駐留在一個類中。甚至語言的設(shè)計者很早的就看到了這個不足,但是顯然在發(fā)布后再去考慮不將行為聯(lián)接到類上就有些晚了。因此在JDK1.1中糾正了這個缺陷,通過添加匿名內(nèi)部類的方式來實現(xiàn)。這只是以一種語法糖的方式來為少量的方法創(chuàng)建一大堆小類,這樣做僅僅是從純功能角度出發(fā),而非從結(jié)構(gòu)上。如果想看有關(guān)Java這方面有趣的文章,請看Steve Yegges的Execution in the Kingdom of Nouns。盡管我非常想要類里面的這個方法,但Java還是強制我去創(chuàng)建一個命令類的實例。這個類本身沒有任何用處:它沒有字段,沒有構(gòu)造器(這個由java自動生成),并且也沒有狀態(tài)

17、。它純粹的目的就是為了在方法里包裝行為。在函數(shù)式語言里,我們通過高階函數(shù)來取代這個模式。如果我不準備用Java的類,那么我可能采用最接近的語義是函數(shù)式編程里面的閉包。列表5顯示了重構(gòu)后的例子,但是使用Groovy代替了Java。列表5, 使用Groovy的閉包代替命令類defwrapInTransaction(command)setupDataInfrastructure()trycommand()completeTransaction()catch(Exceptionex)rollbackTransaction()throwexfinallycleanUp()defaddOrderFrom(

18、cart,userName,order)wrapInTransactionaddorder,userKeyBasedOn(userName)addLineItemsFromcart,order.getOrderKey()在Groovy里面,任何位于大括號之間的東西都是一個代碼塊,并且代碼塊可以被當作參數(shù)來模仿一個高階函數(shù)。在這種情景下,Groovy為你實現(xiàn)了命令模式。Groovy中的每一個閉包塊就是一個Groovy的閉包類型,它包含一個call()方法。當你把一對空括號放到變量后面用于保存閉包實例時,該方法會被自動調(diào)用。Groovy啟用了一些類函數(shù)式編程的行為,通過在語言本身使用相應(yīng)的語法糖來

19、構(gòu)建適當?shù)臄?shù)據(jù)結(jié)構(gòu)。正如我將會逐步展示的那樣,Groovy也包含其他函數(shù)式語言的能力。我將在下面的部分繼續(xù)對閉包和高階函數(shù)做一些有意思的比較。第一級函數(shù)函數(shù)被認為是函數(shù)式語言里面的一等公民,這就意味著函數(shù)可以出現(xiàn)在任何地方,正如其他語言的構(gòu)造體(如變量)那樣。在思考不同解決方案的時候,第一級函數(shù)的存在允許函數(shù)以一種特別的方式來使用,如應(yīng)用同樣的比較操作到相同的數(shù)據(jù)結(jié)構(gòu)上。這就體現(xiàn)了函數(shù)式語言的一個基本思考原則:關(guān)注結(jié)果,而不是過程。在命令式的編程語言里,我必須考慮算法的每一個原子操作。如列表1的代碼顯示的那樣。為了實現(xiàn)數(shù)字歸類器,我不得不精確的識別如何去采集整除因子,這就意為著為了確定一個因子

20、,我不得不寫代碼去遍歷所有數(shù)字。但是像遍歷列表,然后對每一個元素實施操作,這聽起來像是很通用的東西??紤]使用Functional Java框架來重新實現(xiàn)數(shù)字歸類器的代碼,代碼如列表6所示:列表6. 函數(shù)式的數(shù)字歸類器publicclassFNumberClassifierpublicbooleanisFactor(intnumber,intpotential_factor)returnnumber%potential_factor=0;publicListfactors(finalintnumber)returnrange(1,number+1).filter(newF()publicBool

21、eanf(finalIntegeri)returnnumber%i=0;);publicintsum(Listfactors)returnfactors.foldLeft(fj.function.Integers.add,0);publicbooleanisPerfect(intnumber)returnsum(factors(number)-number=number;publicbooleanisAbundant(intnumber)returnsum(factors(number)-numbernumber;publicbooleanisDeficiend(intnumber)retur

22、nsum(factors(number)-numbernumber;列表6和列表2的不同在于兩個方法:sum()和factors()。在Functional Java里, Sum()方法具有List類的foldLeft()方法優(yōu)勢。列表操作概念上的一個具體變化就是被稱之為catamorphism,它是列表折疊上的一般化。在這里“向左折疊”的意思是:1.攜帶一個初始值并組合它到列表的第一個元素上2.攜帶結(jié)果并應(yīng)用相同的操作到下一個元素上3.一直操作直到列表結(jié)束注意當你對一堆數(shù)求和的時候,所做的事情是非常明顯的:從零開始,加上第一關(guān)元素,攜帶結(jié)果去加第二個,重復(fù)這個過程直到所有列表的元素都被處理。Functional Java提供高階函數(shù)(在這個例子里就是Intergers.add枚舉器)并小心翼翼的為你的代碼啟用它。(當然Java真的沒有高階函數(shù),但是你可以通過限制具體的數(shù)據(jù)結(jié)構(gòu)和類型來寫一個較類似的東西)。在列表6里面另一奇妙的方法是factors(),它充分說明了我關(guān)于“關(guān)注結(jié)果,而不

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論