|
結(jié)構(gòu)化編程和面向?qū)ο缶幊潭几镄铝藰I(yè)務(wù)應(yīng)用程序構(gòu)建的方式。但是還存在其他編程模型,有些夢想家還認為這些范式比面向?qū)ο缶幊痰纳a(chǎn)力更高。這篇文章探索 Haskell 研究函數(shù)性編程的基礎(chǔ)。學(xué)習(xí)函數(shù)性編程可以重塑對于 Java? 編程的思考方式。
過去 50 年中,企業(yè)所使用的語言 —— COBOL、C、C++ 和 Java 語言,都是命令式 語言;它們讓您告訴您的程序如何 去完成其任務(wù)。函數(shù)性 編程語言讓您告訴程序去做什么。這篇文章通過介紹 Haskell 來研究函數(shù)性編程。(如果您閱讀過我的跨越邊界 系列中 關(guān)于使用另外一種函數(shù)性語言 Erlang 進行并發(fā)性編程的文章,可能已經(jīng)有了一定的基礎(chǔ)。)
在我研究超越 Java(請參閱 參考資料)時,我采訪的三位專家提到了 Haskell,他們認為我應(yīng)該探索一下這種語言。當(dāng)時,我并不認為市場已經(jīng)為函數(shù)性編程做好了準(zhǔn)備,因為這一范式對于大多數(shù)程序員來說都太陌生了。我現(xiàn)在仍然不認為我們已經(jīng)做好了準(zhǔn)備。但是我開始欣賞函數(shù)性語言帶來的生產(chǎn)力和強大力量。我只是剛剛接觸 Haskell,但這種語言已經(jīng)影響了我使用 Java 和 Ruby 語言解決問題的方式。
命令式語言及其不足
 |
關(guān)于本系列
在 跨越邊界 系列中,作者 Bruce Tate 推動了這樣一個概念:今天的 Java 程序員可以通過學(xué)習(xí)其他方式和語言受益。Java 技術(shù)曾經(jīng)是編程陣營中所有開發(fā)項目的最佳選擇,但這種情況已經(jīng)發(fā)生了變化。其他框架正在塑造 Java 框架的構(gòu)建方式,從其他語言中學(xué)到的概念有助于 Java 編程。所編寫的 Python(或 Ruby、Smalltalk 或 ... 請自由填空)代碼可以改變您編寫 Java 代碼的方式。
本系列介紹了完全不同于 Java 開發(fā),但又直接適用于 Java 開發(fā)的編程概念和技術(shù)。在某些情況下,需要集成之后才能利用這些技術(shù)。在其他情況下,則可以直接應(yīng)用這些概念。單獨的工具不如其他語言和框架影響 Java 社區(qū)的開發(fā)人員、框架、甚至基本手段的這個概念重要。
|
|
命令式編程由一系列帶有明確順序的語句構(gòu)成,它們的算法和編程構(gòu)造嚴(yán)重依賴于應(yīng)用程序的狀態(tài)。很難想像沒有這些特征的編程語言,因為命令式語言 “告訴我如何做” 的方式是如此深刻地確立在我們的日常編程哲學(xué)中。命令式編程明確塑造了您的思維方式。但通過學(xué)習(xí)替代的函數(shù)性編程工具,可以擴展您思考問題的方式。請考慮以下這些命令式結(jié)構(gòu):
- 賦值。命令式編程的用戶對賦值的依賴要超過其他編程技術(shù)。函數(shù)性編程最多允許為每個變量賦值一次。改變值的賦值被叫做 破壞性賦值,是不允許的。例如,多數(shù)函數(shù)性語言不允許
x = x + 1。
- 迭代。命令式編程利用迭代來處理許多不同類型的數(shù)據(jù)結(jié)構(gòu)。許多命令式控制依賴破壞性賦值進行迭代。
- 副作用。在命令式語言中,輸入相同而可能返回不同值的方法,都有副作用。而對應(yīng)用程序的狀態(tài)變量有影響的方法也有副作用。函數(shù)性編程沒有副作用。
如果以前沒用過函數(shù)性語言,那么就很難想像如何編寫沒有破壞性賦值和副作用的應(yīng)用程序。但是這些基本特征給命令性語言帶來了一些更嚴(yán)重的問題:
- 正確性問題。有副作用的方法可能返回一個值,但同時也修改了應(yīng)用程序的狀態(tài)。副作用把驗證程序正確的數(shù)學(xué)問題復(fù)雜化。但是復(fù)雜不僅僅是數(shù)學(xué)問題:副作用還使得測試、理解、分析和記錄程序的行為更加困難。
- 基于順序的 bug。許多優(yōu)化依賴于對關(guān)鍵編程指令重新排序來提高性能。當(dāng)依賴特定順序的程序被重新排序時,錯誤就發(fā)生了。
- 并發(fā)性問題??勺儬顟B(tài)消失時,差不多所有的并發(fā)性問題也隨之消失。Brian Goetz 在 Java Concurrency in Practice(請參閱 參考資料)中,用 “這是愚蠢的可變狀態(tài)“ 規(guī)則結(jié)束了第一章。
不管信還是不信,不必強行規(guī)定操作的順序或承擔(dān)副作用,也可以有效地編程。
函數(shù)性編程簡介
在數(shù)學(xué)中,函數(shù)把每個輸入映射到一個特定的輸出。函數(shù)性編程范式使用數(shù)學(xué)函數(shù)表達程序。函數(shù)性語言并不執(zhí)行命令,而是通過表示、計算數(shù)學(xué)函數(shù)來解決問題。函數(shù)性語言通常有以下兩個特征:
- 不可變的數(shù)據(jù)。純粹的函數(shù)性語言不依賴保存或檢索狀態(tài)的操作,因此不允許破壞性賦值。
- 無副作用。用相同的輸入調(diào)用函數(shù),總是返回相同的值。
對大多數(shù)函數(shù)性語言來說,這有點過于簡化,但是只是過了一點點。函數(shù)叫做單體(monad),被用來以數(shù)學(xué)方式表達狀態(tài)的變化,而 Haskell 這樣的函數(shù)性語言則用單體來處理輸入/輸出并管理狀態(tài)中的變化。
看到函數(shù)性編程的一些局限性后,您可能認為這種編程范式是一種倒退,但請繼續(xù)往下閱讀。函數(shù)性語言不是軟弱無力的。實際上,語言專家們通常相信函數(shù)性語言操作的抽象級別要比面向?qū)ο笳Z言高。它們提供了命令式語言通常不提供的一些工具。在這篇文章中,將看到一些工具的工作效果。
使用 Haskell
有兩個 Haskell 實現(xiàn)值得注意:Hugs 和 Glasgow Haskell Compiler(GHC)(請參閱 參考資料)。還有許多其他 Haskell 編譯器和解釋器,包括 Hugs 和 GHC 的分支,但是它們是主要的兩個。如果剛接觸 Haskell,那么 Hugs 解釋器是個不錯的選擇,因為它安裝和理解起來都比較容易。Hugs 有兩方面嚴(yán)重的限制:它缺乏編譯器,不能使用獨立函數(shù);必須從文件裝入全部函數(shù)。更嚴(yán)謹(jǐn)?shù)某绦騿T會采用 GHC。它的解釋器略慢一些,但是它有編譯器模式,還允許獨立函數(shù)。在這篇文章中,我使用 Hugs,所以如果您想根據(jù)本文編寫代碼,也應(yīng)當(dāng)使用它,因為這兩套軟件使用的術(shù)語略有不同。
用 Hugs 編碼
請下載適合您操作系統(tǒng)的 Hugs (請參閱 參考資料)并啟動它。我把我信任的 Macbook Pro 放在一邊,而用我的 Windows 機器,這是為了得到一個可以快速安裝的環(huán)境。WinHugs 這個 Hugs 實現(xiàn)在 Windows 平臺上有一個簡單的一按即可的安裝程序。
將看到一個帶有 Hugs 提示符的解釋器窗口。啟動即可。輸入一些數(shù)字和數(shù)學(xué)表達式,如清單 1 所示。將看到 Hugs 返回數(shù)學(xué)表達式的結(jié)果。這正是在函數(shù)性語言中期待的行為。
清單 1. 在 Hugs 解釋器中計算
Haskell 98 mode: Restart with command line option -98 to enable extensions
Type :? for help
Hugs>5
5
Hugs>5+4
9
|
Haskell 擁有強大的類型模型。語言是強類型的,這意味著您只能在類型的某個實例上完成允許的操作。(例如,如果想把數(shù)字添加到字符串上,Hugs 會報錯。)Haskell 是靜態(tài)類型化的,所以一旦給變量分配了值,那么變量就會一直維持相同的類型。Haskell 會做一些類型推斷,這意味著它會根據(jù)程序中的語義線索來推斷元素的類型,所以您會看到我在使用 有些函數(shù)時沒有聲明相關(guān)的類型。如果使用類型模糊不清或者在函數(shù)中使用不支持的類型,Haskell 會報錯。Haskell 還有子類型,而且完全是多態(tài)的;這些特性超出了本文的范圍,但如果您對此感興趣,它們也值得研究。
既然已經(jīng)看到了一些原語類型,例如整型,現(xiàn)在可以繼續(xù)了解一些更為復(fù)雜的類型了。通常,一個語言中可用的數(shù)據(jù)結(jié)構(gòu)定義了語言的使用方式。C 使用 struct、Java 使用 class。Haskell 不使用這兩種數(shù)據(jù)結(jié)構(gòu)。
Haskell 中最突出的三種數(shù)據(jù)結(jié)構(gòu)是:tuple、列表(list) 和用戶定義的類型。我將著重介紹前兩種。tuple 要包含于括號 ( ) 之中,它有固定長度。tuple 包含固定類型的原語元素,甚至可以包含其他 tuple 或列表。相比之下,列表的長度可變,由同類元素構(gòu)成。用 [ ] 包含列表。您可使用 Hugs 來體會 tuple 和列表,如清單 2 所示:
清單 2. 表示簡單的 tuple 和列表
Hugs> [1,2]
[1,2]
Hugs> [1,"a"]
ERROR - Cannot infer instance
*** Instance : Num [Char]
*** Expression : [1,"a"]
Hugs> (1,2,3)
(1,2,3)
Hugs> (1,"a")
(1,"a")
Hugs> [(1,2),(3,4)]
[(1,2),(3,4)]
Hugs> [(1,2),(3,4),(5,6,7)]
ERROR - Type error in list
*** Expression : [(1,2),(3,4),(5,6,7)]
*** Term : (5,6,7)
*** Type : (c,d,e)
*** Does not match : (a,b)
|
在清單 2 中,可以看到 tuple 中的每個元素可以是不同類型的,但列表中的元素必須是相同類型的。而且,如果您使用一個 tuple 列表,那么每個 tuple 的長度必須相同,每個 tuple 中的第 n 個元素必須與列表中所有其他 tuple 中的第 n 個元素匹配。
如您所料,Haskell 有許多在列表上操作的函數(shù)。最簡單的就是 head 和 tail。 head 返回列表的第一個元素,tail 返回其他的元素。清單 3 顯示了一些簡單的列表函數(shù):
清單 3. 簡單的 Haskell 列表函數(shù)
Hugs> [1,2,3,4,5]
[1,2,3,4,5]
Hugs> length [1,2,3,4,5]
5
Hugs> head [1,2,3,4,5]
1
Hugs> tail [1,2,3,4,5]
[2,3,4,5]
|
在清單 3 中,可以看到 head 返回了一個元素,tail 返回了一列元素。稍后還會看到(在 編寫函數(shù) 中)這些函數(shù)如何構(gòu)成了 Haskell 中眾多遞歸函數(shù)的基礎(chǔ)。
在構(gòu)建列表時,使用 : 操作符,叫做構(gòu)造(cons) 操作符(用來構(gòu)造)。構(gòu)建列表時,只是把元素傳遞給另一個列表??梢园言S多構(gòu)建操作串在一起。
字符串只是字符列表的語法代稱而已,像 [1,2,3] 這樣的列表則是 1:2:3:[] 的語法代稱。這個特性使得字符串操作更容易實現(xiàn)。清單 4 顯示了構(gòu)造操作符的的工作方式以及如何用一個字符序列構(gòu)建一個字符串:
清單 4. 用構(gòu)造操作符構(gòu)建列表
Hugs> 6:[]
[6]
Hugs> 6:[7]
[6,7]
Hugs> 6:7:[]
[6,7]
Hugs> ‘d‘:‘o‘:‘g‘:‘ ‘:‘h‘:‘a(chǎn)‘:‘s‘:‘ ‘:‘f‘:‘l‘:‘e‘:‘a(chǎn)‘:‘s‘:[]
"dog has fleas"
|
在 Haskell 中,您會發(fā)現(xiàn)在字符串和列表中這種語法代稱非常普遍。 但是只要記?。阂磺卸际呛瘮?shù)。
把函數(shù)當(dāng)成數(shù)據(jù)
Haskell 允許把函數(shù)當(dāng)成數(shù)據(jù)。這項重要的功能讓眾多的 Haskell 函數(shù)可以接受函數(shù)作為參數(shù)。這個策略讓您能夠用不同的方式把函數(shù)應(yīng)用到函數(shù)的每個元素。清單 5 顯示了一系列函數(shù),它們把函數(shù)應(yīng)用到列表的每個元素:
清單 5. 把函數(shù)作為參數(shù)傳遞
Hugs> :l Char
Char> map toUpper "Hello"
"HELLO"
Char> filter isUpper "To Be or Not to Be"
"TBNB"
Char> foldr (max) 0 [1,2,3,2,1]
3
Char> foldr (+) 0 [1,2,3,2,1]
9
|
:l 命令裝入模塊。然后可以調(diào)用 Char 模塊中的函數(shù)。(其他版本的 Haskell 支持 Char.toUpper,但是 Hugs 不支持,所以要先裝入模塊,然后再利用模塊中的函數(shù)。)第一個語句在列表的每個元素上調(diào)用函數(shù)(本例中為 toUpper,用于把字符轉(zhuǎn)成大寫)。因為 Haskell 的字符串就是字符列表,所以得到的是 "HELLO" 字符串。filter 函數(shù)為列表的每個元素調(diào)用一個測試函數(shù),測試函數(shù)返回布爾值,當(dāng)測試函數(shù)返回 True 時,還包含列表的元素。
fold 函數(shù)要略微復(fù)雜一些。它有兩種形式:foldl 和 foldr。fold 函數(shù)接受一個函數(shù)、一個元素和一個列表??梢赃@樣來看待 fold 函數(shù):
- 從列表的左側(cè)(
foldl)右側(cè)(foldr)開始放置元素。
- 把操作符放在列表的所有元素之間。
- 從列表的一側(cè)到另一側(cè)應(yīng)用操作符,對于
foldl 是從左側(cè)開始,對于 foldr 是從右側(cè)開始。
例如,foldr (+) 0 [1,2,3] 可歸納為 1 + (2 + (3 + 0)) 也就是 6。有些時候,順序會有影響,這就是為什么 Haskell 既提供了 foldr(右聯(lián),從右至左構(gòu)建)又提供了 foldl (左聯(lián),從左至右構(gòu)建)的原因。在處理列表時,fold 函數(shù)提供了累積任何二進制計算結(jié)果的好方法。
把函數(shù)組合起來
在 Haskell 程序中,可以用許多不同的方式組合函數(shù)。用復(fù)雜的方式組合函數(shù),是函數(shù)性語言生產(chǎn)力的關(guān)鍵,這是因為這可不斷提高抽象的層次。
 |
|
用復(fù)雜的方式組合函數(shù),是函數(shù)性語言生產(chǎn)力的關(guān)鍵,這是因為這可不斷提高抽象的層次。
|
|
例如,假設(shè)有一個 Java 應(yīng)用程序,它計算句子中大寫字母的數(shù)量。需要對列表進行迭代,每遇到一個大寫字母,就要將一個本地變量加 1。在 Haskell 中,只需用 length (filter (isUpper) "To Be or Not to Be") 取得過濾列表的長度即可。Haskell 程序員就是這樣避免了使用破壞性更新。每個函數(shù)都在內(nèi)部保存中間結(jié)果,程序員不必考慮這類細節(jié)。
編寫函數(shù)
如果您使用的是 Hugs,那么需要在獨立的源文件中編寫函數(shù)。WinHugs 可以很好的集成我的編輯器,所以不會造成太大的負擔(dān)。應(yīng)當(dāng)把函數(shù)放在模塊中,然后用 :l 命令裝入模塊。在清單 5 中已經(jīng)看到,我裝入了系統(tǒng)模塊 Char。模塊名稱以及包含模塊的文件,要用大寫字母開始.函數(shù)名用小寫字母開始。Haskell 程序文件的擴展名是 .hs。
可以注意到:Haskell 程序頻繁地用遞歸來解決問題。Java 開發(fā)人員由于性能的原因和堆棧深度的限制,通常會避免遞歸。在處理遞歸時,Haskell 有兩大優(yōu)勢:Haskell 優(yōu)化了尾部遞歸,Haskell 是惰性的。
尾部遞歸優(yōu)化意味著當(dāng)遞歸發(fā)生在函數(shù)末尾時,編譯器或解釋器可以把遞歸表示成迭代。尾部遞歸優(yōu)化沒有堆棧開銷,所以這個策略降低了把遞歸處理成簡單迭代的成本。為了理解 “惰性” 的含義,請把以下函數(shù)輸入名為 Generate.hs 的文件:
這個函數(shù)是 1 的無窮列表。更精確地說,generate 通過構(gòu)造操作符,把 1 添加到 generate —— 最初是個空列表。如果裝入并執(zhí)行這個函數(shù),就會進入無窮遞歸循環(huán),因為沒有什么能夠停止遞歸。但奇怪的是,可以在應(yīng)用程序中使用 generate 的結(jié)果,如清單 6 所示:
清單 6. Generate.hs 中的惰性遞歸
Hugs> :l Generate
Main> head generate
1
Main> head (tail generate)
1
|
雖然 generate 代表 1 的無窮列表,但 Haskell 只計算列表中自己需要的部分。在清單 6 中,第一個命令裝入函數(shù),第二個得到頭(或第一個)元素,第三個命令得到末尾的頭(或第二個)元素。Haskell 只計算列表的頭兩個元素。剩下的被延遲,只在需要的時候計算。這種惰性處理的風(fēng)格使得函數(shù)性語言的效率出人意料,而且支持其他編程語言中得不到的叫做無限流 的強大抽象(請參閱 參考資料)。
在大多數(shù)教程中可以發(fā)現(xiàn)的大多數(shù)經(jīng)典 Haskell 函數(shù)都是遞歸的數(shù)學(xué)函數(shù),例如 Fibonacci 函數(shù)和階乘。Fibonacci 序列的定義是:由 1 和 1 開始的序列前兩個數(shù)字的和。所以,F(xiàn)ibonacci 序列的前五個數(shù)字是 1、1、(1+1) = 2、(1+2) = 3 和 (2+3) = 5。Haskell 實現(xiàn)的這個序列與它的數(shù)學(xué)定義非常相似,如清單 7 所示:
清單 7. fib.hs 中的 Fibonacci 序列
fib 1 = 1
fib 2 = 1
fib x = fib(x-1) + fib(x-2)
|
可以把清單 7 中的代碼輸入到名為 Fib.hs 的文件中,用 :l fib 裝入它,并輸入 fib(4) 來運行它,生成序列的第四個數(shù)字。請注意,我不需要聲明保存中間結(jié)果的變量。在這個示例中,可以看到更高級別抽象的示例。如果您的問題集恰好適合 Haskell,那么更高級別的抽象就適合您。如果不適合,這種更高級別的抽象將使您陷入麻煩。
可以用與 Fibonacci 序列基本相同的方式對待階乘。x 的階乘就是從 1 到 x 的所有數(shù)字的乘積。在 Haskell 中,可以定義一個計算階乘的函數(shù),如清單 8 所示:
清單 8. 計算階乘
factorial 1 = 1
factorial x = x * factorial(x-1)
|
對列表的遍歷也采用同樣的工作方式。處理列表時,要處理第一個節(jié)點,然后處理列表剩下的部分(剩下的也是列表)。清單 9 顯示了計算列表中所有元素之和的一個遞歸函數(shù):
清單 9. 計算數(shù)字列表的總和
sum [] = 0
sum (h:t) = h + sum(t)
|
如果您以前沒見過這種模式,那么可能需要一點時間來習(xí)慣它。第一行說明空列表的和是 0。第二行代表的概念在幾乎所有函數(shù)性語言中都很普遍。(h:t) 把列表表示成 tuple,列表的頭(包含第一個元素)在 h 內(nèi),列表剩余的元素包含在 t 中。由于尾部遞歸優(yōu)化,這種方式是對列表進行迭代的一種非常簡單的方式。思維過程與代碼的匹配非常好:做第一件事,剩下的事后面再做;什么都不剩時,就完成了。
用同樣的方式也可以遍歷非常復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。只要多付出一點功夫,就能表達 B 樹 —— B 樹的每個節(jié)點都容納一個值和兩個分支。用 Haskell 表示的簡單 B 樹形式如下:
data Tree a = Null | Node (Tree a) a (Tree a)
|
在這個示例中,data 是構(gòu)建抽象類型的手段。a 是類型。它告訴 Haskell:樹可以可以什么都不包含,也可以是由樹構(gòu)成的,樹后面跟著值,后面又跟著另一個樹。樹中的每個節(jié)點都有一個值、一個左子節(jié)點和一個右子節(jié)點。(子節(jié)點可以是 null,也可以不為 null。)
遍歷樹的代碼看起來與遍歷列表的代碼非常像。如果想要 null 樹的匯總為 null,典型節(jié)點的匯總是左樹的匯總加上值的匯總和右樹的匯總,請看清單 10:
清單 10. 遍歷樹
sum_tree :: Tree Integer -> Integer
sum_tree Null = 0
sum_tree (Node left val right) = sum_tree left + val + sum_tree right
|
在清單 10 中,第一行定義了函數(shù)使用的類型。 sum_tree :: Tree Integer -> Integer 意味著函數(shù) sum_tree 要接受一個 Integer 的 Tree 作為參數(shù),并返回 Integer。下兩行代碼指定函數(shù)。空樹的匯總是零,其他樹的匯總是左樹加上 Integer 的值再加上右樹的值。
現(xiàn)在可以把它放在 Tree.hs 中,如清單 11 所示:
清單 11. Tree.hs
data Tree a = Null | Node (Tree a) a (Tree a)
sum_tree :: Tree Integer -> Integer
sum_tree Null = 0
sum_tree (Node left val right) = sum_tree left + val + sum_tree right
t::Tree Integer
t=Node Null 4 Null
|
在這個示例中,t::Tree Integer 把 Tree 綁定到一個類型,下一行把 t 綁定到一個值??梢杂?:l Tree 把清單 11 裝入 Hugs,并輸入 sum_tree t。也可以表示更復(fù)雜的樹,例如 Node Null 6 (Node Null 7 Null) 表示代碼一個右節(jié)點的樹。可以把不同的樹放在 t 中,然后再次運行 sum_tree 來感受一下。(請記住,每次試驗時都需要重新裝入函數(shù)。)
回顧函數(shù)性 “限制”
Haskell 能夠很好地處理數(shù)據(jù)結(jié)構(gòu)。因為把字符串看成是字符的簡單列表,所以 Haskell 不僅能很好地處理各種形式的文本,您還可以有效地處理樹、復(fù)雜的數(shù)學(xué)問題以及嵌套結(jié)構(gòu)。函數(shù)性語言被頻繁地用來構(gòu)建編譯器或者解析語言的工具,因為語言通常是用語法樹表示的。因為可以在數(shù)據(jù)結(jié)構(gòu)中包含函數(shù),所以函數(shù)性語言構(gòu)成了元編程的優(yōu)秀平臺。
現(xiàn)在已經(jīng)看到了函數(shù)性語言的基礎(chǔ),接下來可以開始了解,您要如何在沒有副作用和對狀態(tài)只有有限支持的環(huán)境下生活。正如我在前面提到過的,單體可以表示狀態(tài)的變化。但是即使用了單體,管理狀態(tài)也是困難的 —— 所以不要嘗試這樣做。相反,可以考慮通過定義不需要中間狀態(tài)的復(fù)合函數(shù)來解決問題的方式。不要考慮迭代,而應(yīng)使用遞歸或以下某個函數(shù) —— map、filter 或 fold,它們會把表達式應(yīng)用到列表的每個元素。要使用函數(shù)性語言,只需要放棄命令式編程的風(fēng)格。學(xué)習(xí)用更加函數(shù)性的風(fēng)格進行編程,有以下好處:
- 可以用更緊湊的方式表示思想。
- 通過避免副作用,可以限制應(yīng)用程序某些類型的 bug 的影響。
- 通過減少本地變量,可以讓代碼更具伸縮性。
函數(shù)性語言極大地影響了編程的思考方式。多年以來,MIT 的程序員最初使用了 Lisp,因為這種語言能夠有力地幫助程序員學(xué)習(xí)如何在更高層次的抽象上工作。
本文僅僅觸及到了函數(shù)性語言的皮毛。我鼓勵您下載 Haskell 或其他函數(shù)性語言,親自動手去體驗它。我猜,它會改造您對編程的思考方式。
|