約翰麥卡錫和他的學生于 1958 年展開 Lisp 的初次實現(xiàn)工作。 Lisp 是繼 FORTRAN 之后,仍在使用的最古老的程序語言。?λ?更值得注意的是,它仍走在程序語言技術的最前面。懂 Lisp 的程序員會告訴你,有某種東西使 Lisp 與眾不同。
Lisp 與眾不同的部分原因是,它被設計成能夠自己進化。你能用 Lisp 定義新的 Lisp 操作符。當新的抽象概念風行時(如面向對象程序設計),我們總是發(fā)現(xiàn)這些新概念在 Lisp 是最容易來實現(xiàn)的。Lisp 就像生物的 DNA 一樣,這樣的語言永遠不會過時。
為什么要學 Lisp?因為它讓你能做一些其它語言做不到的事情。如果你只想寫一個函數(shù)來返回小于?n
?的數(shù)字總和,那么用 Lisp 和 C 是差不多的:
; Lisp /* C */
(defun sum (n) int sum(int n){
(let ((s 0)) int i, s = 0;
(dotimes (i n s) for(i = 0; i < n; i++)
(incf s i)))) s += i;
return(s);
}
如果你只想做這種簡單的事情,那用什么語言都不重要。假設你想寫一個函數(shù),輸入一個數(shù)?n
?,返回把?n
?與傳入?yún)?shù) (argument)相加的函數(shù)。
; Lisp
(defun addn (n)
#'(lambda (x)
(+ x n)))
在 C 語言中?addn
?怎么實現(xiàn)?你根本寫不出來。
你可能會想,誰會想做這樣的事情?程序語言教你不要做它們沒有提供的事情。你得針對每個程序語言,用其特定的思維來寫程序,而且想得到你所不能描述的東西是很困難的。當我剛開始編程時 ── 用 Baisc ── 我不知道什么是遞歸,因為我根本不知道有這個東西。我是用 Basic 在思考。我只能用迭代的概念表達算法,所以我怎么會知道遞歸呢?
如果你沒聽過詞法閉包 「Lexical Closure」?(上述?addn
?的范例),相信我, Lisp 程序員一直在使用它。很難找到任何長度的 Common Lisp 程序,沒有用到閉包的好處。在 112 頁前,你自己會持續(xù)使用它。
閉包僅是其中一個我們在別的語言找不到的抽象概念之一。另一個更有價值的 Lisp 特點是, Lisp 程序是用 Lisp 的數(shù)據(jù)結構來表示。這表示你可以寫出會寫程序的程序。人們真的需要這個嗎?沒錯 ── 它們叫做宏,有經(jīng)驗的程序員也一直在使用它。學到 173 頁你就可以自己寫出自己的宏了。
有了宏、閉包以及運行期類型,Lisp 凌駕在面向對象程序設計之上。如果你了解上面那句話,也許你不應該閱讀此書。你得充分了解 Lisp 才能明白為什么此言不虛。但這不是空泛之言。這是一個重要的論點,并且在 17 章用程序相當明確的證明了這點。
第二章到第十三章會循序漸進地介紹所有你需要理解第 17 章程序的概念。你的努力會有所回報:你會感到在 C++ 編程是窒礙難行的,就像有經(jīng)驗的 C++ 程序員用 Basic 編程會感到窒息一樣。更加鼓舞人心的是,如果我們思考為什么會有這種感覺。 編寫 Basic 對于平常用 C++ 編程是令人感到窒息的,是因為有經(jīng)驗的 C++ 程序員知道一些用 Basic 不可能表達出來的技術。同樣地,學習 Lisp 不僅教你學會一門新的語言 ── 它教你嶄新的并且更強大的程序思考方法。
如上一節(jié)所提到的, Lisp 賦予你別的語言所沒有的工具。不僅僅如此,就 Lisp 帶來的新特性來說 ── 自動內(nèi)存管理 (automatic memory management),顯式類型 (manifest typing),閉包 (closures)等 ── 每一項都使得編程變得如此簡單。結合起來,它們組成了一個關鍵的部分,使得一種新的編程方式是有可能的。
Lisp 被設計成可擴展的:讓你定義自己的操作符。這是可能的,因為 Lisp 是由和你程序一樣的函數(shù)與宏所構成的。所以擴展 Lisp 就和寫一個 Lisp 程序一樣簡單。事實上,它是如此的容易(和有用),以至于擴展語言自身成了標準實踐。當你在用 Lisp 語言編程時,你也在創(chuàng)造一個適合你的程序的語言。你由下而上地,也由上而下地工作。
幾乎所有的程序,都可以從訂作適合自己所需的語言中受益。然而越復雜的程序,由下而上的程序設計就顯得越有價值。一個由下而上所設計出來的程序,可寫成一系列的層,每層擔任上一層的程序語言。?TeX?是最早使用這種方法所寫的程序之一。你可以用任何語言由下而上地設計程序,但 Lisp 是本質上最適合這種方法的工具。
由下而上的編程方法,自然發(fā)展出可擴展的軟件。如果你把由下而上的程序設計的原則,想成你程序的最上層,那這層就成為使用者的程序語言。正因可擴展的思想深植于 Lisp 當中,使得 Lisp 成為實現(xiàn)可擴展軟件的理想語言。三個 1980 年代最成功的程序提供 Lisp 作為擴展自身的語言:?GNU Emacs?,?Autocad?,和?Interleaf?。
由下而上的編程方法,也是得到可重用軟件的最好方法。寫可重用軟件的本質是把共同的地方從細節(jié)中分離出來,而由下而上的編程方法本質地創(chuàng)造這種分離。與其努力撰寫一個龐大的應用,不如努力創(chuàng)造一個語言,用相對小的努力在這語言上撰寫你的應用。和應用相關的特性集中在最上層,以下的層可以組成一個適合這種應用的語言 ── 還有什么比程序語言更具可重用性的呢?
Lisp 讓你不僅編寫出更復雜的程序,而且寫的更快。 Lisp 程序通常很簡短 ── Lisp 給了你更高的抽象化,所以你不用寫太多代碼。就像?Frederick Brooks?所指出的,編程所花的時間主要取決于程序的長度。因此僅僅根據(jù)這個單獨的事實,就可以推斷出用 Lisp 編程所花的時間較少。這種效果被 Lisp 的動態(tài)特點放大了:在 Lisp 中,編輯-編譯-測試循環(huán)短到使編程像是即時的。
更高的抽象化與互動的環(huán)境,能改變各個機構開發(fā)軟件的方式。術語快速建型描述了一種始于 Lisp 的編程方法:在 Lisp 里,你可以用比寫規(guī)格說明更短的時間,寫一個原型出來,而這種原型是高度抽象化的,可作為一個比用英語所寫的更好的規(guī)格說明。而且 Lisp 讓你可以輕易的從原型轉成產(chǎn)品軟件。當寫一個考慮到速度的 Common Lisp 程序時,通過現(xiàn)代編譯器的編譯,Lisp 與其他的高階語言所寫的程序運行得一樣快。
除非你相當熟悉 Lisp ,這個簡介像是無意義的言論和冠冕堂皇的聲明。Lisp 凌駕面向對象程序設計??*你創(chuàng)造適合你程序的語言?*Lisp 編程是即時的??這些說法是什么意思?現(xiàn)在這些說法就像是枯竭的湖泊。隨著你學到更多實際的 Lisp 特色,見過更多可運行的程序,這些說法就會被實際經(jīng)驗之水所充滿,而有了明確的形狀。
本書的目標之一是不僅是教授 Lisp 語言,而是教授一種新的編程方法,這種方法因為有了 Lisp 而有可能實現(xiàn)。這是一種你在未來會見得更多的方法。隨著開發(fā)環(huán)境變得更強大,程序語言變得更抽象, Lisp 的編程風格正逐漸取代舊的規(guī)劃-然后-實現(xiàn)?(plan-and-implement)的模式。
在舊的模式中,錯誤永遠不應該出現(xiàn)。事前辛苦訂出縝密的規(guī)格說明,確保程序完美的運行。理論上聽起來不錯。不幸地,規(guī)格說明是人寫的,也是人來實現(xiàn)的。實際上結果是,?規(guī)劃-然后-實現(xiàn)?模型不太有效。
身為 OS/360 的項目經(jīng)理,?Frederick Brooks?非常熟悉這種傳統(tǒng)的模式。他也非常熟悉它的后果:
任何 OS/360 的用戶很快的意識到它應該做得更好...再者,產(chǎn)品推遲,用了更多的內(nèi)存,成本是估計的好幾倍,效能一直不好,直到第一版后的好幾個版本更新,效能才算還可以。
而這卻描述了那個時代最成功系統(tǒng)之一。
舊模式的問題是它忽略了人的局限性。在舊模式中,你打賭規(guī)格說明不會有嚴重的缺失,實現(xiàn)它們不過是把規(guī)格轉成代碼的簡單事情。經(jīng)驗顯示這實在是非常壞的賭注。打賭規(guī)格說明是誤導的,程序到處都是臭蟲 (bug) 會更保險一點。
這其實就是新的編程模式所假設的。設法盡量降低錯誤的成本,而不是希望人們不犯錯。錯誤的成本是修補它所花費的時間。使用強大的語言跟好的開發(fā)環(huán)境,這種成本會大幅地降低。編程風格可以更多地依靠探索,較少地依靠事前規(guī)劃。
規(guī)劃是一種必要之惡。它是評估風險的指標:越是危險,預先規(guī)劃就顯得更重要。強大的工具降低了風險,也降低了規(guī)劃的需求。程序的設計可以從最有用的信息來源中受益:過去實作程序的經(jīng)驗。
Lisp 風格從 1960 年代一直朝著這個方向演進。你在 Lisp 中可以如此快速地寫出原型,以致于你已歷經(jīng)好幾個設計和實現(xiàn)的循環(huán),而在舊的模式當中,你可能才剛寫完規(guī)格說明。你不必擔心設計的缺失,因為你將更快地發(fā)現(xiàn)它們。你也不用擔心有那么多臭蟲。當你用函數(shù)式風格來編程,你的臭蟲只有局部的影響。當你使用一種很抽象的語言,某些臭蟲(如迷途指針)不再可能發(fā)生,而剩下的臭蟲很容易找出,因為你的程序更短了。當你有一個互動的開發(fā)環(huán)境,你可以即時修補臭蟲,不必經(jīng)歷 編輯,編譯,測試的漫長過程。
Lisp 風格會這么演進是因為它產(chǎn)生的結果。聽起來很奇怪,少的規(guī)劃意味著更好的設計。技術史上相似的例子不勝枚舉。一個相似的變革發(fā)生在十五世紀的繪畫圈里。在油畫流行前,畫家使用一種叫做蛋彩的材料來作畫。蛋彩不能被混和或涂掉。犯錯的代價非常高,也使得畫家變得保守。后來隨著油畫顏料的出現(xiàn),作畫風格有了大幅地改變。油畫“允許你再來一次”這對困難主題的處理,像是畫人體,提供了決定性的有利條件。
新的材料不僅使畫家更容易作畫了。它使新的更大膽的作畫方式成為可能。 Janson 寫道:
如果沒有油畫顏料,弗拉芒大師們的征服可見的現(xiàn)實的口號就會大打折扣。于是,從技術的角度來說,也是如此,但他們當之無愧地稱得上是“現(xiàn)代繪畫之父”,油畫顏料從此以后成為畫家的基本顏料。
做為一種介質,蛋彩與油畫顏料一樣美麗。但油畫顏料的彈性給想像力更大的發(fā)揮空間 ── 這是決定性的因素。
程序設計正經(jīng)歷著相同的改變。新的介質像是“動態(tài)的面向對象語言” ── 即 Lisp 。這不是說我們所有的軟件在幾年內(nèi)都要用 Lisp 來寫。從蛋彩到油畫的轉變也不是一夜完成的;油彩一開始只在領先的藝術中心流行,而且經(jīng)?;旌现安蕘硎褂?。我們現(xiàn)在似乎正處于這個階段。 Lisp 被大學,研究室和某些頂尖的公司所使用。同時,從 Lisp 借鑒的思想越來越多地出現(xiàn)在主流語言中:交互式編程環(huán)境 (interactive programming environment)、垃圾回收(garbage collection)、運行期類型 (run-time typing),僅舉其中幾個。
強大的工具正降低探索的風險。這對程序員來說是好消息,因為意味者我們可以從事更有野心的項目。油畫的確有這個效果。采用油畫后的時期正是繪畫的黃金時期。類似的跡象正在程序設計的領域中發(fā)生。
更多建議: