午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

為什么Lisp語言如此先進(jìn)?

 Gemmy 2010-10-15
為什么Lisp語言如此先進(jìn)?


作者:Paul Graham

譯者:阮一峰

英文原文:Revenge of the Nerds

(節(jié)選自即將出版的《黑客與畫家》中譯本)


一、

如果我們把流行的編程語言,以這樣的順序排列:Java、Perl、Python、Ruby。你會發(fā)現(xiàn),排在越后面的語言,越像Lisp。

Python模仿Lisp,甚至把許多Lisp黑客認(rèn)為屬于設(shè)計錯誤的功能,也一起模仿了。至于Ruby,如果回到1975年,你聲稱它是一種Lisp方言,沒有人會反對。

編程語言現(xiàn)在的發(fā)展,不過剛剛趕上1958年Lisp語言的水平。

二、

1958年,John McCarthy設(shè)計了Lisp語言。我認(rèn)為,當(dāng)前最新潮的編程語言,只是實現(xiàn)了他在1958年的設(shè)想而已。

這怎么可能呢?計算機(jī)技術(shù)的發(fā)展,不是日新月異嗎?1958年的技術(shù),怎么可能超過今天的水平呢?

讓我告訴你原因。

這是因為John McCarthy本來沒打算把Lisp設(shè)計成編程語言,至少不是我們現(xiàn)在意義上的編程語言。他的原意只是想做一種理論演算,用更簡潔的方式定義圖靈機(jī)。

所以,為什么上個世紀(jì)50年代的編程語言,到現(xiàn)在還沒有過時?簡單說,因為這種語言本質(zhì)上不是一種技術(shù),而是數(shù)學(xué)。數(shù)學(xué)是不會過時的。你不應(yīng)該把 Lisp語言與50年代的硬件聯(lián)系在一起,而是應(yīng)該把它與快速排序(Quicksort)算法進(jìn)行類比。這種算法是1960年提出的,至今仍然是最快的通 用排序方法。

三、

Fortran語言也是上個世紀(jì)50年代出現(xiàn)的,并且一直使用至今。它代表了語言設(shè)計的一種完全不同的方向。Lisp是無意中從純理論發(fā)展為編程語 言,而Fortran從一開始就是作為編程語言設(shè)計出來的。但是,今天我們把Lisp看成高級語言,而把Fortran看成一種相當(dāng)?shù)蛯哟蔚恼Z言。

1956年,F(xiàn)ortran剛誕生的時候,叫做Fortran I,與今天的Fortran語言差別極大。Fortran I實際上是匯編語言加上數(shù)學(xué),在某些方面,還不如今天的匯編語言強(qiáng)大。比如,它不支持子程序,只有分支跳轉(zhuǎn)結(jié)構(gòu)(branch)。

Lisp和Fortran代表了編程語言發(fā)展的兩大方向。前者的基礎(chǔ)是數(shù)學(xué),后者的基礎(chǔ)是硬件架構(gòu)。從那時起,這兩大方向一直在互相靠攏。Lisp 剛設(shè)計出來的時候,就很強(qiáng)大,接下來的二十年,它提高了自己的運行速度。而那些所謂的主流語言,把更快的運行速度作為設(shè)計的出發(fā)點,然后再用超過四十年的 時間,一步步變得更強(qiáng)大。

直到今天,最高級的主流語言,也只是剛剛接近Lisp的水平。雖然已經(jīng)很接近了,但還是沒有Lisp那樣強(qiáng)大。

四、

Lisp語言誕生的時候,就包含了9種新思想。其中一些我們今天已經(jīng)習(xí)以為常,另一些則剛剛在其他高級語言中出現(xiàn),至今還有2種是Lisp獨有的。按照被大眾接受的程度,這9種思想依次是:

1. 條件結(jié)構(gòu)(即"if-then-else"結(jié)構(gòu))?,F(xiàn)在大家都覺得這是理所當(dāng)然的,但是Fortran I就沒有這個結(jié)構(gòu),它只有基于底層機(jī)器指令的goto結(jié)構(gòu)。

2. 函數(shù)也是一種數(shù)據(jù)類型。在Lisp語言中,函數(shù)與整數(shù)或字符串一樣,也屬于數(shù)據(jù)類型的一種。它有自己的字面表示形式(literal representation),能夠儲存在變量中,也能當(dāng)作參數(shù)傳遞。一種數(shù)據(jù)類型應(yīng)該有的功能,它都有。

3. 遞歸。Lisp是第一種支持遞歸函數(shù)的高級語言。

4. 變量的動態(tài)類型。在Lisp語言中,所有變量實際上都是指針,所指向的值有類型之分,而變量本身沒有。復(fù)制變量就相當(dāng)于復(fù)制指針,而不是復(fù)制它們指向的數(shù)據(jù)。

5. 垃圾回收機(jī)制。

6. 程序由表達(dá)式(expression)組成。Lisp程序是一些表達(dá)式區(qū)塊的集合,每個表達(dá)式都返回一個值。這與Fortran和大多數(shù)后來的語言都截然不同,它們的程序由表達(dá)式和語句(statement)組成。

區(qū)分表達(dá)式和語句,在Fortran I中是很自然的,因為它不支持語句嵌套。所以,如果你需要用數(shù)學(xué)式子計算一個值,那就只有用表達(dá)式返回這個值,沒有其他語法結(jié)構(gòu)可用,因為否則就無法處理這個值。

后來,新的編程語言支持區(qū)塊結(jié)構(gòu)(block),這種限制當(dāng)然也就不存在了。但是為時已晚,表達(dá)式和語句的區(qū)分已經(jīng)根深蒂固。它從Fortran擴(kuò)散到Algol語言,接著又?jǐn)U散到它們兩者的后繼語言。

7. 符號(symbol)類型。符號實際上是一種指針,指向儲存在哈希表中的字符串。所以,比較兩個符號是否相等,只要看它們的指針是否一樣就行了,不用逐個字符地比較。

8. 代碼使用符號和常量組成的樹形表示法(notation)。

9. 無論什么時候,整個語言都是可用的。Lisp并不真正區(qū)分讀取期、編譯期和運行期。你可以在讀取期編譯或運行代碼;也可以在編譯期讀取或運行代碼;還可以在運行期讀取或者編譯代碼。

在讀取期運行代碼,使得用戶可以重新調(diào)整(reprogram)Lisp的語法;在編譯期運行代碼,則是Lisp宏的工作基礎(chǔ);在運行期編譯代碼, 使得Lisp可以在Emacs這樣的程序中,充當(dāng)擴(kuò)展語言(extension language);在運行期讀取代碼,使得程序之間可以用S-表達(dá)式(S-expression)通信,近來XML格式的出現(xiàn)使得這個概念被重新"發(fā) 明"出來了。

五、

Lisp語言剛出現(xiàn)的時候,它的思想與其他編程語言大相徑庭。后者的設(shè)計思想主要由50年代后期的硬件決定。隨著時間流逝,流行的編程語言不斷更新?lián)Q代,語言設(shè)計思想逐漸向Lisp靠攏。

思想1到思想5已經(jīng)被廣泛接受,思想6開始在主流編程語言中出現(xiàn),思想7在Python語言中有所實現(xiàn),不過似乎沒有專用的語法。

思想8可能是最有意思的一點。它與思想9只是由于偶然原因,才成為Lisp語言的一部分,因為它們不屬于John McCarthy的原始構(gòu)想,是由他的學(xué)生Steve Russell自行添加的。它們從此使得Lisp看上去很古怪,但也成為了這種語言最獨一無二的特點。Lisp古怪的形式,倒不是因為它的語法很古怪,而 是因為它根本沒有語法,程序直接以解析樹(parse tree)的形式表達(dá)出來。在其他語言中,這種形式只是經(jīng)過解析在后臺產(chǎn)生,但是Lisp直接采用它作為表達(dá)形式。它由列表構(gòu)成,而列表則是Lisp的基 本數(shù)據(jù)結(jié)構(gòu)。

用一門語言自己的數(shù)據(jù)結(jié)構(gòu)來表達(dá)該語言,這被證明是非常強(qiáng)大的功能。思想8和思想9,意味著你可以寫出一種能夠自己編程的程序。這可能聽起來很怪異,但是對于Lisp語言卻是再普通不過。最常用的做法就是使用宏。

術(shù)語"宏"在Lisp語言中,與其他語言中的意思不一樣。Lisp宏無所不包,它既可能是某樣表達(dá)式的縮略形式,也可能是一種新語言的編譯器。如果你想真正地理解Lisp語言,或者想拓寬你的編程視野,那么你必須學(xué)習(xí)宏。

就我所知,宏(采用Lisp語言的定義)目前仍然是Lisp獨有的。一個原因是為了使用宏,你大概不得不讓你的語言看上去像Lisp一樣古怪。另一 個可能的原因是,如果你想為自己的語言添上這種終極武器,你從此就不能聲稱自己發(fā)明了新語言,只能說發(fā)明了一種Lisp的新方言。

我把這件事當(dāng)作笑話說出來,但是事實就是如此。如果你創(chuàng)造了一種新語言,其中有car、cdr、cons、quote、cond、atom、eq這 樣的功能,還有一種把函數(shù)寫成列表的表示方法,那么在它們的基礎(chǔ)上,你完全可以推導(dǎo)出Lisp語言的所有其他部分。事實上,Lisp語言就是這樣定義 的,John McCarthy把語言設(shè)計成這個樣子,就是為了讓這種推導(dǎo)成為可能。

六、

就算Lisp確實代表了目前主流編程語言不斷靠近的一個方向,這是否意味著你就應(yīng)該用它編程呢?

如果使用一種不那么強(qiáng)大的語言,你又會有多少損失呢?有時不采用最尖端的技術(shù),不也是一種明智的選擇嗎?這么多人使用主流編程語言,這本身不也說明那些語言有可取之處嗎?

另一方面,選擇哪一種編程語言,許多項目是無所謂的,反正不同的語言都能完成工作。一般來說,條件越苛刻的項目,強(qiáng)大的編程語言就越能發(fā)揮作用。但 是,無數(shù)的項目根本沒有苛刻條件的限制。大多數(shù)的編程任務(wù),可能只要寫一些很小的程序,然后用膠水語言把這些小程序連起來就行了。你可以用自己熟悉的編程 語言,或者用對于特定項目來說有著最強(qiáng)大函數(shù)庫的語言,來寫這些小程序。如果你只是需要在Windows應(yīng)用程序之間傳遞數(shù)據(jù),使用Visual Basic照樣能達(dá)到目的。

那么,Lisp的編程優(yōu)勢體現(xiàn)在哪里呢?

七、

語言的編程能力越強(qiáng)大,寫出來的程序就越短(當(dāng)然不是指字符數(shù)量,而是指獨立的語法單位)。

代碼的數(shù)量很重要,因為開發(fā)一個程序耗費的時間,主要取決于程序的長度。如果同一個軟件,一種語言寫出來的代碼比另一種語言長三倍,這意味著你開發(fā) 它耗費的時間也會多三倍。而且即使你多雇傭人手,也無助于減少開發(fā)時間,因為當(dāng)團(tuán)隊規(guī)模超過某個門檻時,再增加人手只會帶來凈損失。Fred Brooks在他的名著《人月神話》(The Mythical Man-Month)中,描述了這種現(xiàn)象,我的所見所聞印證了他的說法。

如果使用Lisp語言,能讓程序變得多短?以Lisp和C的比較為例,我聽到的大多數(shù)說法是C代碼的長度是Lisp的7倍到10倍。但是最 近,New Architect雜志上有一篇介紹ITA軟件公司的文章,里面說"一行Lisp代碼相當(dāng)于20行C代碼",因為此文都是引用ITA總裁的話,所以我想這 個數(shù)字來自ITA的編程實踐。 如果真是這樣,那么我們可以相信這句話。ITA的軟件,不僅使用Lisp語言,還同時大量使用C和C++,所以這是他們的經(jīng)驗談。

根據(jù)上面的這個數(shù)字,如果你與ITA競爭,而且你使用C語言開發(fā)軟件,那么ITA的開發(fā)速度將比你快20倍。如果你需要一年時間實現(xiàn)某個功能,它只需要不到三星期。反過來說,如果某個新功能,它開發(fā)了三個月,那么你需要五年才能做出來。

你知道嗎?上面的對比,還只是考慮到最好的情況。當(dāng)我們只比較代碼數(shù)量的時候,言下之意就是假設(shè)使用功能較弱的語言,也能開發(fā)出同樣的軟件。但是事 實上,程序員使用某種語言能做到的事情,是有極限的。如果你想用一種低層次的語言,解決一個很難的問題,那么你將會面臨各種情況極其復(fù)雜、乃至想不清楚的 窘境。

所以,當(dāng)我說假定你與ITA競爭,你用五年時間做出的東西,ITA在Lisp語言的幫助下只用三個月就完成了,我指的五年還是一切順利、沒有犯錯誤、也沒有遇到太大麻煩的五年。事實上,按照大多數(shù)公司的實際情況,計劃中五年完成的項目,很可能永遠(yuǎn)都不會完成。

我承認(rèn),上面的例子太極端。ITA似乎有一批非常聰明的黑客,而C語言又是一種很低層次的語言。但是,在一個高度競爭的市場中,即使開發(fā)速度只相差兩三倍,也足以使得你永遠(yuǎn)處在落后的位置。

附錄:編程能力

為了解釋我所說的語言編程能力不一樣,請考慮下面的問題。我們需要寫一個函數(shù),它能夠生成累加器,即這個函數(shù)接受一個參數(shù)n,然后返回另一個函數(shù),后者接受參數(shù)i,然后返回n增加(increment)了i后的值。

Common Lisp的寫法如下:

(defun foo (n)
(lambda (i) (incf n i)))

Ruby的寫法幾乎完全相同:

def foo (n)
lambda {|i| n += i } end

Perl 5的寫法則是:

sub foo {
my ($n) = @_;
sub {$n += shift}
}

這比Lisp和Ruby的版本,有更多的語法元素,因為在Perl語言中,你不得不手工提取參數(shù)。

Smalltalk的寫法稍微比Lisp和Ruby的長一點:

foo: n
|s|
s := n.
^[:i| s := s+i. ]

因為在Smalltalk中,局部變量(lexical variable)是有效的,但是你無法給一個參數(shù)賦值,因此不得不設(shè)置了一個新變量,接受累加后的值。

Javascript的寫法也比Lisp和Ruby稍微長一點,因為Javascript依然區(qū)分語句和表達(dá)式,所以你需要明確指定return語句,來返回一個值:

function foo (n) {
return function (i) {
return n += i } }

(實事求是地說,Perl也保留了語句和表達(dá)式的區(qū)別,但是使用了典型的Perl方式處理,使你可以省略return。)

如果想把Lisp/Ruby/Perl/Smalltalk/Javascript的版本改成Python,你會遇到一些限制。因為Python并 不完全支持局部變量,你不得不創(chuàng)造一種數(shù)據(jù)結(jié)構(gòu),來接受n的值。而且盡管Python確實支持函數(shù)數(shù)據(jù)類型,但是沒有一種字面量的表示方式 (literal representation)可以生成函數(shù)(除非函數(shù)體只有一個表達(dá)式),所以你需要創(chuàng)造一個命名函數(shù),把它返回。最后的寫法如下:

def foo (n):
s = [n]
def bar (i):
s[0] += i
return s[0]
return bar

Python用戶完全可以合理地質(zhì)疑,為什么不能寫成下面這樣:

def foo (n):
return lambda i: return n += i

或者:

def foo (n):
lambda i: n += i

我猜想,Python有一天會支持這樣的寫法。(如果你不想等到Python慢慢進(jìn)化到更像Lisp,你總是可以直接......)

在面向?qū)ο缶幊痰恼Z言中,你能夠在有限程度上模擬一個閉包(即一個函數(shù),通過它可以引用由包含這個函數(shù)的代碼所定義的變量)。你定義一個類 (class),里面有一個方法和一個屬性,用于替換封閉作用域(enclosing scope)中的所有變量。這有點類似于讓程序員自己做代碼分析,本來這應(yīng)該是由支持局部作用域的編譯器完成的。如果有多個函數(shù),同時指向相同的變量,那 么這種方法就會失效,但是在這個簡單的例子中,它已經(jīng)足夠了。

Python高手看來也同意,這是解決這個問題的比較好的方法,寫法如下:

def foo (n):
class acc:
def _ _init_ _ (self, s):
self.s = s
def inc (self, i):
self.s += i
return self.s
return acc (n).inc

或者

class foo:
def _ _init_ _ (self, n):
self.n = n
def _ _call_ _ (self, i):
self.n += i
return self.n

我添加這一段,原因是想避免Python愛好者說我誤解這種語言。但是,在我看來,這兩種寫法好像都比第一個版本更復(fù)雜。你實際上就是在做同樣的 事,只不過劃出了一個獨立的區(qū)域,保存累加器函數(shù),區(qū)別只是保存在對象的一個屬性中,而不是保存在列表(list)的頭(head)中。使用這些特殊的內(nèi) 部屬性名(尤其是__call__),看上去并不像常規(guī)的解法,更像是一種破解。

在Perl和Python的較量中,Python黑客的觀點似乎是認(rèn)為Python比Perl更優(yōu)雅,但是這個例子表明,最終來說,編程能力決定了優(yōu)雅。Perl的寫法更簡單(包含更少的語法元素),盡管它的語法有一點丑陋。

其他語言怎么樣?前文曾經(jīng)提到過Fortran、C、C++、Java和Visual Basic,看上去使用它們,根本無法解決這個問題。Ken Anderson說,Java只能寫出一個近似的解法:

public interface Inttoint {
public int call (int i);
}

public static Inttoint foo (final int n) {
return new Inttoint () {
int s = n;
public int call (int i) {
s = s + i;
return s;
}};
}

這種寫法不符合題目要求,因為它只對整數(shù)有效。

當(dāng)然,我說使用其他語言無法解決這個問題,這句話并不完全正確。所有這些語言都是圖靈等價的,這意味著嚴(yán)格地說,你能使用它們之中的任何一種語言, 寫出任何一個程序。那么,怎樣才能做到這一點呢?就這個小小的例子而言,你可以使用這些不那么強(qiáng)大的語言,寫一個Lisp解釋器就行了。

這樣做聽上去好像開玩笑,但是在大型編程項目中,卻不同程度地廣泛存在。因此,有人把它總結(jié)出來,起名為"格林斯潘第十定律"(Greenspun's Tenth Rule):

"任何C或Fortran程序復(fù)雜到一定程度之后,都會包含一個臨時開發(fā)的、只有一半功能的、不完全符合規(guī)格的、到處都是bug的、運行速度很慢的Common Lisp實現(xiàn)。"

如果你想解決一個困難的問題,關(guān)鍵不是你使用的語言是否強(qiáng)大,而是好幾個因素同時發(fā)揮作用(a)使用一種強(qiáng)大的語言,(b)為這個難題寫一個事實上 的解釋器,或者(c)你自己變成這個難題的人肉編譯器。在Python的例子中,這樣的處理方法已經(jīng)開始出現(xiàn)了,我們實際上就是自己寫代碼,模擬出編譯器 實現(xiàn)局部變量的功能。

這種實踐不僅很普遍,而且已經(jīng)制度化了。舉例來說,在面向?qū)ο缶幊痰氖澜缰?,我們大量聽?模式"(pattern)這個詞,我覺得那些"模式"就 是現(xiàn)實中的因素(c),也就是人肉編譯器。 當(dāng)我在自己的程序中,發(fā)現(xiàn)用到了模式,我覺得這就表明某個地方出錯了。程序的形式,應(yīng)該僅僅反映它所要解決的問題。代碼中其他任何外加的形式,都是一個信 號,(至少對我來說)表明我對問題的抽象還不夠深,也經(jīng)常提醒我,自己正在手工完成的事情,本應(yīng)該寫代碼,通過宏的擴(kuò)展自動實現(xiàn)

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多