第一章:簡(jiǎn)介

2018-02-24 15:50 更新

約翰麥卡錫和他的學(xué)生于 1958 年展開(kāi) Lisp 的初次實(shí)現(xiàn)工作。 Lisp 是繼 FORTRAN 之后,仍在使用的最古老的程序語(yǔ)言。?λ?更值得注意的是,它仍走在程序語(yǔ)言技術(shù)的最前面。懂 Lisp 的程序員會(huì)告訴你,有某種東西使 Lisp 與眾不同。

Lisp 與眾不同的部分原因是,它被設(shè)計(jì)成能夠自己進(jìn)化。你能用 Lisp 定義新的 Lisp 操作符。當(dāng)新的抽象概念風(fēng)行時(shí)(如面向?qū)ο蟪绦蛟O(shè)計(jì)),我們總是發(fā)現(xiàn)這些新概念在 Lisp 是最容易來(lái)實(shí)現(xiàn)的。Lisp 就像生物的 DNA 一樣,這樣的語(yǔ)言永遠(yuǎn)不會(huì)過(guò)時(shí)。

1.1 新的工具 (New Tools)

為什么要學(xué) Lisp?因?yàn)樗屇隳茏鲆恍┢渌Z(yǔ)言做不到的事情。如果你只想寫一個(gè)函數(shù)來(lái)返回小于?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);
                          }

如果你只想做這種簡(jiǎn)單的事情,那用什么語(yǔ)言都不重要。假設(shè)你想寫一個(gè)函數(shù),輸入一個(gè)數(shù)?n?,返回把?n?與傳入?yún)?shù) (argument)相加的函數(shù)。

; Lisp
(defun addn (n)
  #'(lambda (x)
      (+ x n)))

在 C 語(yǔ)言中?addn?怎么實(shí)現(xiàn)?你根本寫不出來(lái)。

你可能會(huì)想,誰(shuí)會(huì)想做這樣的事情?程序語(yǔ)言教你不要做它們沒(méi)有提供的事情。你得針對(duì)每個(gè)程序語(yǔ)言,用其特定的思維來(lái)寫程序,而且想得到你所不能描述的東西是很困難的。當(dāng)我剛開(kāi)始編程時(shí) ── 用 Baisc ── 我不知道什么是遞歸,因?yàn)槲腋静恢烙羞@個(gè)東西。我是用 Basic 在思考。我只能用迭代的概念表達(dá)算法,所以我怎么會(huì)知道遞歸呢?

如果你沒(méi)聽(tīng)過(guò)詞法閉包 「Lexical Closure」?(上述?addn?的范例),相信我, Lisp 程序員一直在使用它。很難找到任何長(zhǎng)度的 Common Lisp 程序,沒(méi)有用到閉包的好處。在 112 頁(yè)前,你自己會(huì)持續(xù)使用它。

閉包僅是其中一個(gè)我們?cè)趧e的語(yǔ)言找不到的抽象概念之一。另一個(gè)更有價(jià)值的 Lisp 特點(diǎn)是, Lisp 程序是用 Lisp 的數(shù)據(jù)結(jié)構(gòu)來(lái)表示。這表示你可以寫出會(huì)寫程序的程序。人們真的需要這個(gè)嗎?沒(méi)錯(cuò) ── 它們叫做宏,有經(jīng)驗(yàn)的程序員也一直在使用它。學(xué)到 173 頁(yè)你就可以自己寫出自己的宏了。

有了宏、閉包以及運(yùn)行期類型,Lisp 凌駕在面向?qū)ο蟪绦蛟O(shè)計(jì)之上。如果你了解上面那句話,也許你不應(yīng)該閱讀此書。你得充分了解 Lisp 才能明白為什么此言不虛。但這不是空泛之言。這是一個(gè)重要的論點(diǎn),并且在 17 章用程序相當(dāng)明確的證明了這點(diǎn)。

第二章到第十三章會(huì)循序漸進(jìn)地介紹所有你需要理解第 17 章程序的概念。你的努力會(huì)有所回報(bào):你會(huì)感到在 C++ 編程是窒礙難行的,就像有經(jīng)驗(yàn)的 C++ 程序員用 Basic 編程會(huì)感到窒息一樣。更加鼓舞人心的是,如果我們思考為什么會(huì)有這種感覺(jué)。 編寫 Basic 對(duì)于平常用 C++ 編程是令人感到窒息的,是因?yàn)橛薪?jīng)驗(yàn)的 C++ 程序員知道一些用 Basic 不可能表達(dá)出來(lái)的技術(shù)。同樣地,學(xué)習(xí) Lisp 不僅教你學(xué)會(huì)一門新的語(yǔ)言 ── 它教你嶄新的并且更強(qiáng)大的程序思考方法。

1.2 新的技術(shù) (New Techniques)

如上一節(jié)所提到的, Lisp 賦予你別的語(yǔ)言所沒(méi)有的工具。不僅僅如此,就 Lisp 帶來(lái)的新特性來(lái)說(shuō) ── 自動(dòng)內(nèi)存管理 (automatic memory management),顯式類型 (manifest typing),閉包 (closures)等 ── 每一項(xiàng)都使得編程變得如此簡(jiǎn)單。結(jié)合起來(lái),它們組成了一個(gè)關(guān)鍵的部分,使得一種新的編程方式是有可能的。

Lisp 被設(shè)計(jì)成可擴(kuò)展的:讓你定義自己的操作符。這是可能的,因?yàn)?Lisp 是由和你程序一樣的函數(shù)與宏所構(gòu)成的。所以擴(kuò)展 Lisp 就和寫一個(gè) Lisp 程序一樣簡(jiǎn)單。事實(shí)上,它是如此的容易(和有用),以至于擴(kuò)展語(yǔ)言自身成了標(biāo)準(zhǔn)實(shí)踐。當(dāng)你在用 Lisp 語(yǔ)言編程時(shí),你也在創(chuàng)造一個(gè)適合你的程序的語(yǔ)言。你由下而上地,也由上而下地工作。

幾乎所有的程序,都可以從訂作適合自己所需的語(yǔ)言中受益。然而越復(fù)雜的程序,由下而上的程序設(shè)計(jì)就顯得越有價(jià)值。一個(gè)由下而上所設(shè)計(jì)出來(lái)的程序,可寫成一系列的層,每層擔(dān)任上一層的程序語(yǔ)言。?TeX?是最早使用這種方法所寫的程序之一。你可以用任何語(yǔ)言由下而上地設(shè)計(jì)程序,但 Lisp 是本質(zhì)上最適合這種方法的工具。

由下而上的編程方法,自然發(fā)展出可擴(kuò)展的軟件。如果你把由下而上的程序設(shè)計(jì)的原則,想成你程序的最上層,那這層就成為使用者的程序語(yǔ)言。正因可擴(kuò)展的思想深植于 Lisp 當(dāng)中,使得 Lisp 成為實(shí)現(xiàn)可擴(kuò)展軟件的理想語(yǔ)言。三個(gè) 1980 年代最成功的程序提供 Lisp 作為擴(kuò)展自身的語(yǔ)言:?GNU Emacs?,?Autocad?,和?Interleaf?。

由下而上的編程方法,也是得到可重用軟件的最好方法。寫可重用軟件的本質(zhì)是把共同的地方從細(xì)節(jié)中分離出來(lái),而由下而上的編程方法本質(zhì)地創(chuàng)造這種分離。與其努力撰寫一個(gè)龐大的應(yīng)用,不如努力創(chuàng)造一個(gè)語(yǔ)言,用相對(duì)小的努力在這語(yǔ)言上撰寫你的應(yīng)用。和應(yīng)用相關(guān)的特性集中在最上層,以下的層可以組成一個(gè)適合這種應(yīng)用的語(yǔ)言 ── 還有什么比程序語(yǔ)言更具可重用性的呢?

Lisp 讓你不僅編寫出更復(fù)雜的程序,而且寫的更快。 Lisp 程序通常很簡(jiǎn)短 ── Lisp 給了你更高的抽象化,所以你不用寫太多代碼。就像?Frederick Brooks?所指出的,編程所花的時(shí)間主要取決于程序的長(zhǎng)度。因此僅僅根據(jù)這個(gè)單獨(dú)的事實(shí),就可以推斷出用 Lisp 編程所花的時(shí)間較少。這種效果被 Lisp 的動(dòng)態(tài)特點(diǎn)放大了:在 Lisp 中,編輯-編譯-測(cè)試循環(huán)短到使編程像是即時(shí)的。

更高的抽象化與互動(dòng)的環(huán)境,能改變各個(gè)機(jī)構(gòu)開(kāi)發(fā)軟件的方式。術(shù)語(yǔ)快速建型描述了一種始于 Lisp 的編程方法:在 Lisp 里,你可以用比寫規(guī)格說(shuō)明更短的時(shí)間,寫一個(gè)原型出來(lái),而這種原型是高度抽象化的,可作為一個(gè)比用英語(yǔ)所寫的更好的規(guī)格說(shuō)明。而且 Lisp 讓你可以輕易的從原型轉(zhuǎn)成產(chǎn)品軟件。當(dāng)寫一個(gè)考慮到速度的 Common Lisp 程序時(shí),通過(guò)現(xiàn)代編譯器的編譯,Lisp 與其他的高階語(yǔ)言所寫的程序運(yùn)行得一樣快。

除非你相當(dāng)熟悉 Lisp ,這個(gè)簡(jiǎn)介像是無(wú)意義的言論和冠冕堂皇的聲明。Lisp 凌駕面向?qū)ο蟪绦蛟O(shè)計(jì)??*你創(chuàng)造適合你程序的語(yǔ)言?*Lisp 編程是即時(shí)的??這些說(shuō)法是什么意思?現(xiàn)在這些說(shuō)法就像是枯竭的湖泊。隨著你學(xué)到更多實(shí)際的 Lisp 特色,見(jiàn)過(guò)更多可運(yùn)行的程序,這些說(shuō)法就會(huì)被實(shí)際經(jīng)驗(yàn)之水所充滿,而有了明確的形狀。

1.3 新的方法 (New Approach)

本書的目標(biāo)之一是不僅是教授 Lisp 語(yǔ)言,而是教授一種新的編程方法,這種方法因?yàn)橛辛?Lisp 而有可能實(shí)現(xiàn)。這是一種你在未來(lái)會(huì)見(jiàn)得更多的方法。隨著開(kāi)發(fā)環(huán)境變得更強(qiáng)大,程序語(yǔ)言變得更抽象, Lisp 的編程風(fēng)格正逐漸取代舊的規(guī)劃-然后-實(shí)現(xiàn)?(plan-and-implement)的模式。

在舊的模式中,錯(cuò)誤永遠(yuǎn)不應(yīng)該出現(xiàn)。事前辛苦訂出縝密的規(guī)格說(shuō)明,確保程序完美的運(yùn)行。理論上聽(tīng)起來(lái)不錯(cuò)。不幸地,規(guī)格說(shuō)明是人寫的,也是人來(lái)實(shí)現(xiàn)的。實(shí)際上結(jié)果是,?規(guī)劃-然后-實(shí)現(xiàn)?模型不太有效。

身為 OS/360 的項(xiàng)目經(jīng)理,?Frederick Brooks?非常熟悉這種傳統(tǒng)的模式。他也非常熟悉它的后果:

任何 OS/360 的用戶很快的意識(shí)到它應(yīng)該做得更好...再者,產(chǎn)品推遲,用了更多的內(nèi)存,成本是估計(jì)的好幾倍,效能一直不好,直到第一版后的好幾個(gè)版本更新,效能才算還可以。

而這卻描述了那個(gè)時(shí)代最成功系統(tǒng)之一。

舊模式的問(wèn)題是它忽略了人的局限性。在舊模式中,你打賭規(guī)格說(shuō)明不會(huì)有嚴(yán)重的缺失,實(shí)現(xiàn)它們不過(guò)是把規(guī)格轉(zhuǎn)成代碼的簡(jiǎn)單事情。經(jīng)驗(yàn)顯示這實(shí)在是非常壞的賭注。打賭規(guī)格說(shuō)明是誤導(dǎo)的,程序到處都是臭蟲(chóng) (bug) 會(huì)更保險(xiǎn)一點(diǎn)。

這其實(shí)就是新的編程模式所假設(shè)的。設(shè)法盡量降低錯(cuò)誤的成本,而不是希望人們不犯錯(cuò)。錯(cuò)誤的成本是修補(bǔ)它所花費(fèi)的時(shí)間。使用強(qiáng)大的語(yǔ)言跟好的開(kāi)發(fā)環(huán)境,這種成本會(huì)大幅地降低。編程風(fēng)格可以更多地依靠探索,較少地依靠事前規(guī)劃。

規(guī)劃是一種必要之惡。它是評(píng)估風(fēng)險(xiǎn)的指標(biāo):越是危險(xiǎn),預(yù)先規(guī)劃就顯得更重要。強(qiáng)大的工具降低了風(fēng)險(xiǎn),也降低了規(guī)劃的需求。程序的設(shè)計(jì)可以從最有用的信息來(lái)源中受益:過(guò)去實(shí)作程序的經(jīng)驗(yàn)。

Lisp 風(fēng)格從 1960 年代一直朝著這個(gè)方向演進(jìn)。你在 Lisp 中可以如此快速地寫出原型,以致于你已歷經(jīng)好幾個(gè)設(shè)計(jì)和實(shí)現(xiàn)的循環(huán),而在舊的模式當(dāng)中,你可能才剛寫完規(guī)格說(shuō)明。你不必?fù)?dān)心設(shè)計(jì)的缺失,因?yàn)槟銓⒏斓匕l(fā)現(xiàn)它們。你也不用擔(dān)心有那么多臭蟲(chóng)。當(dāng)你用函數(shù)式風(fēng)格來(lái)編程,你的臭蟲(chóng)只有局部的影響。當(dāng)你使用一種很抽象的語(yǔ)言,某些臭蟲(chóng)(如迷途指針)不再可能發(fā)生,而剩下的臭蟲(chóng)很容易找出,因?yàn)槟愕某绦蚋塘?。?dāng)你有一個(gè)互動(dòng)的開(kāi)發(fā)環(huán)境,你可以即時(shí)修補(bǔ)臭蟲(chóng),不必經(jīng)歷 編輯,編譯,測(cè)試的漫長(zhǎng)過(guò)程。

Lisp 風(fēng)格會(huì)這么演進(jìn)是因?yàn)樗a(chǎn)生的結(jié)果。聽(tīng)起來(lái)很奇怪,少的規(guī)劃意味著更好的設(shè)計(jì)。技術(shù)史上相似的例子不勝枚舉。一個(gè)相似的變革發(fā)生在十五世紀(jì)的繪畫圈里。在油畫流行前,畫家使用一種叫做蛋彩的材料來(lái)作畫。蛋彩不能被混和或涂掉。犯錯(cuò)的代價(jià)非常高,也使得畫家變得保守。后來(lái)隨著油畫顏料的出現(xiàn),作畫風(fēng)格有了大幅地改變。油畫“允許你再來(lái)一次”這對(duì)困難主題的處理,像是畫人體,提供了決定性的有利條件。

新的材料不僅使畫家更容易作畫了。它使新的更大膽的作畫方式成為可能。 Janson 寫道:

如果沒(méi)有油畫顏料,弗拉芒大師們的征服可見(jiàn)的現(xiàn)實(shí)的口號(hào)就會(huì)大打折扣。于是,從技術(shù)的角度來(lái)說(shuō),也是如此,但他們當(dāng)之無(wú)愧地稱得上是“現(xiàn)代繪畫之父”,油畫顏料從此以后成為畫家的基本顏料。

做為一種介質(zhì),蛋彩與油畫顏料一樣美麗。但油畫顏料的彈性給想像力更大的發(fā)揮空間 ── 這是決定性的因素。

程序設(shè)計(jì)正經(jīng)歷著相同的改變。新的介質(zhì)像是“動(dòng)態(tài)的面向?qū)ο笳Z(yǔ)言” ── 即 Lisp 。這不是說(shuō)我們所有的軟件在幾年內(nèi)都要用 Lisp 來(lái)寫。從蛋彩到油畫的轉(zhuǎn)變也不是一夜完成的;油彩一開(kāi)始只在領(lǐng)先的藝術(shù)中心流行,而且經(jīng)?;旌现安蕘?lái)使用。我們現(xiàn)在似乎正處于這個(gè)階段。 Lisp 被大學(xué),研究室和某些頂尖的公司所使用。同時(shí),從 Lisp 借鑒的思想越來(lái)越多地出現(xiàn)在主流語(yǔ)言中:交互式編程環(huán)境 (interactive programming environment)、垃圾回收(garbage collection)、運(yùn)行期類型 (run-time typing),僅舉其中幾個(gè)。

強(qiáng)大的工具正降低探索的風(fēng)險(xiǎn)。這對(duì)程序員來(lái)說(shuō)是好消息,因?yàn)橐馕墩呶覀兛梢詮氖赂幸靶牡捻?xiàng)目。油畫的確有這個(gè)效果。采用油畫后的時(shí)期正是繪畫的黃金時(shí)期。類似的跡象正在程序設(shè)計(jì)的領(lǐng)域中發(fā)生。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)