函數(shù)式編程如何才有一席之地
除非你生活中與世隔絕的深山老林里,否則你應(yīng)該知道,在眾多的所謂頂級(jí)編程高手(alpha geeks)中,函數(shù)式編程是十分盛行的。也許你已經(jīng)使用了某種函數(shù)式編程語(yǔ)言。如果你是在使用很傳統(tǒng)的編程語(yǔ)言,例如Java或C#,你應(yīng)該知道了,這些語(yǔ)言很快就將引入一些函數(shù)式編程特征。就在這美麗的新世界即將來(lái)到之際,就在我們摩拳擦掌打算大干一番之前,我想,現(xiàn)在應(yīng)該是我們暫停一下、反省一下函數(shù)式編程在我們的日常應(yīng)用開發(fā)中是否合適的好時(shí)機(jī)。
什么是函數(shù)式編程?簡(jiǎn)單的回答:一切都是數(shù)學(xué)函數(shù)。函數(shù)式編程語(yǔ)言里也可以有對(duì)象,但通常這些對(duì)象都是恒定不變的——要么是函數(shù)參數(shù),要什么是函數(shù)返回值。函數(shù)式編程語(yǔ)言里沒(méi)有for/next循環(huán),因?yàn)檫@些邏輯意味著有狀態(tài)的改變。相替代的是,這種循環(huán)邏輯在函數(shù)式編程語(yǔ)言里是通過(guò)遞歸、把函數(shù)當(dāng)成參數(shù)傳遞的方式實(shí)現(xiàn)的。
為什么要使用函數(shù)式編程
擁護(hù)者說(shuō)函數(shù)式編程能開發(fā)出更高效的軟件,而反對(duì)者說(shuō)反之亦然。我感覺(jué)雙方的觀點(diǎn)都有問(wèn)題。我可以輕松的證明函數(shù)式編程能使你更難寫出針對(duì)編譯器優(yōu)化的代碼,或者相較于傳統(tǒng)語(yǔ)言的代碼,JIT編譯器對(duì)于函數(shù)式代碼會(huì)編譯出更慢的程序。命令式編程語(yǔ)言(imperative programming languages)語(yǔ)法跟底層的計(jì)算機(jī)硬件指令間有著很相似的對(duì)應(yīng)關(guān)系,但函數(shù)式編程語(yǔ)言卻沒(méi)有這種特征。結(jié)果就是,編譯器處理函數(shù)式編程語(yǔ)言時(shí)更費(fèi)力。
然而,優(yōu)秀的編譯器能把函數(shù)式編程中的閉包、tail調(diào)用、或lambda表達(dá)式轉(zhuǎn)換成跟傳統(tǒng)語(yǔ)言中l(wèi)oop循環(huán)或其它表達(dá)式等效的代碼。這需要多做一些工作。如果你在尋找一本厚達(dá)1600頁(yè)的關(guān)于這方面的好書,我推薦你《Optimizing Compilers for Modern Architectures: A Dependence-based Approach》和《Advanced Compiler Design and Implementation》?;蛘吣阋部梢允褂肎CC或任何具有多階段編譯功能、能生成匯編代碼的編譯器自己去證明這一點(diǎn)。
對(duì)于為什么要使用函數(shù)式編程,這有一個(gè)更好的論據(jù),現(xiàn)代的應(yīng)用程序都會(huì)牽涉到多核計(jì)算機(jī)上的并行運(yùn)算功能,程序狀態(tài)就成了一個(gè)問(wèn)題。所有的命令式語(yǔ)言,包括面向?qū)ο笳Z(yǔ)言,在涉及多線程時(shí),都會(huì)遇到共享對(duì)象的狀態(tài)修改問(wèn)題。這就是死鎖、堆棧跟蹤、低級(jí)處理器緩存命中率低等問(wèn)題的根源。如果對(duì)象沒(méi)有狀態(tài),這些問(wèn)題就不存在了。
在很多地方使用函數(shù)式編程或函數(shù)式編程語(yǔ)言都是非常適合的,甚至是最好的選擇。對(duì)于純函數(shù)計(jì)算,函數(shù)式編程明顯的比命令式編程更合適。但對(duì)于商業(yè)軟件或其它普通應(yīng)用軟件,你不能不說(shuō)這正好要顛倒過(guò)來(lái)。就像Martin Fowler著名的闡述,“傻子都能寫出計(jì)算機(jī)可讀懂的代碼。優(yōu)秀的程序員寫出的是人能讀懂的代碼。”而函數(shù)式編程寫出的代碼就是讓人一眼望去不可讀。
幾段代碼就能讓你知道我說(shuō)的是什么意思。來(lái)自Erlang語(yǔ)言的代碼例子:
- -module(listsort).
- -export([by_length/1]).
- by_length(Lists) ->
- qsort(Lists, fun(A,B) -> A < B end).
- qsort([], _)-> [];
- qsort([Pivot|Rest], Smaller) ->
- qsort([X || X <- Rest, Smaller(X,Pivot)], Smaller)
- ++ [Pivot] ++
- qsort([Y || Y <- Rest, not(Smaller(Y, Pivot))], Smaller).
這個(gè)是Haskell語(yǔ)言的:
- -- file: ch05/Prettify.hs
- pretty width x = best 0 [x]
- where best col (d:ds) =
- case d of
- Empty -> best col ds
- Char c -> c : best (col + 1) ds
- Text s -> s ++ best (col + length s) ds
- Line -> '\n' : best 0 ds
- a `Concat` b -> best col (a:b:ds)
- a `Union` b -> nicest col (best col (a:ds))
- (best col (b:ds))
- best _ _ = ""
- nicest col a b | (width - least) `fits` a = a
- | otherwise = b
- where least = min width col
人 vs 機(jī)器
一個(gè)不怎么樣的程序員一般都能從一段命令式的代碼中很快的看出其基本的功用——甚至這是一種他從未見過(guò)的語(yǔ)言。然而雖然你也能從一段函數(shù)式代碼里分析出它的功用,但你絕對(duì)不可能簡(jiǎn)單幾眼就能看出來(lái)。不像命令式代碼,函數(shù)式代碼并不體現(xiàn)出簡(jiǎn)單的語(yǔ)言結(jié)構(gòu)。它展現(xiàn)的都是數(shù)學(xué)結(jié)構(gòu)。
我們的編程經(jīng)歷了從紙帶打孔到匯編到宏匯編到C語(yǔ)言(高級(jí)宏匯編)再到抽象出了很多老實(shí)機(jī)器上復(fù)制運(yùn)算的高等編程語(yǔ)言。每一步都使我們?cè)絹?lái)越接近《星際迷航4》里的場(chǎng)景:遇到麻煩的Scott對(duì)他的鼠標(biāo)說(shuō)出指令(“Hello computer“)。數(shù)十年的進(jìn)步使得編程語(yǔ)言越來(lái)越容易被人類閱讀和理解,函數(shù)式編程的語(yǔ)法是在把時(shí)鐘指針往后撥。
函數(shù)式編程能解決并行運(yùn)算的狀態(tài)問(wèn)題,但付出的代價(jià)是失去人類可讀性。函數(shù)式編程也許完全可以用于任何環(huán)境開發(fā),它甚至可以通過(guò)定義面向領(lǐng)域(domain-specific)的編程語(yǔ)言來(lái)拉近人類語(yǔ)言和計(jì)算機(jī)語(yǔ)言之間的距離。但它糟糕的語(yǔ)法使得它極不適合常規(guī)目的的編程開發(fā)。
不要這么著急的判斷潮流——特別對(duì)于那些不想有太多風(fēng)險(xiǎn)的項(xiàng)目。
英文原文:Functional programming: A step backward
本文鏈接:http://www.aqee.net/functional-programming-a-step-backward/