Lisp 的爭議
由于 Lisp 語言的 “過于靈活而神秘存在” 的特性使得 Lisp 成了世界上最受爭議的編程語言,實(shí)際上獨(dú)樹一幟的 Lisp 也在(針對不同的產(chǎn)品,,總有熱衷「語言比較」的人們引發(fā)語言優(yōu)勢性的爭論)類的問題得到庇護(hù),,因?yàn)?Lisp 語言本身不是針對開發(fā)項(xiàng)目而誕生的語言,這種性質(zhì)完美的避開了針對項(xiàng)目類型而友好的語言之間的比較話題,。盡管如此,,還有很多人用「Lisp 永遠(yuǎn)成不了編程主流語言」來反駁極少 Hacker 所說的「Lisp 是 Hacker 們最好的神器」。 雖然 「Lisp 永遠(yuǎn)成為不了主流的編程語言」這個問題本身就是偽命題,,因?yàn)?Lisp 從來沒打算成為主流的編程語言,,Lisp 與眾不同的部分原因是,它被設(shè)計(jì)成能夠自己進(jìn)化,。你能用 Lisp 定義新的 Lisp 操作符,。當(dāng)新的抽象概念風(fēng)行時(shí)(如面向?qū)ο蟪绦蛟O(shè)計(jì)),我們總是發(fā)現(xiàn)這些新概念在 Lisp 是最容易來實(shí)現(xiàn)的,。Lisp 就像生物的 DNA 一樣,,雖然 Lisp 沒打算成為主流語言,但這樣的語言永遠(yuǎn)不會過時(shí),。
Lisp 永遠(yuǎn)成不了編程主流語言
Lisp 語言是第二古老的高級編程語言,。許多的黑客和開發(fā)者對 Lisp 推崇備至,Paul Graham 甚至說 “編程語言現(xiàn)在的發(fā)展,,不過剛剛趕上 1958 年 Lisp 語言的水平”,。
然而這樣先進(jìn)的語言在現(xiàn)在使用的編程語言從來沒有排到前 20,聽說它的人不少,,用的人卻非常少,。
許多人對 Lisp 語言的第一印象就是一層層的括號,很老的關(guān)于蘇聯(lián)黑客偷到 Lisp 源碼的最后一頁全是括號的笑話就不用再說了,。
造成 Lisp 程序如此多括號的原因就是 「S 表達(dá)式」,。所謂 S 表達(dá)式,是指一種以人類可讀的文本形式表達(dá)半結(jié)構(gòu)化數(shù)據(jù)的約定,,是點(diǎn)對表示法的形式定義,。
S 表達(dá)式 是 Lisp 語言的鮮明特點(diǎn),使數(shù)據(jù)和代碼形式統(tǒng)一,,讓使用者有能力對程序和數(shù)據(jù)進(jìn)行統(tǒng)一處理,。
Lisp 語言使用這統(tǒng)一的 S 表達(dá)式,,讓 A+B 變成了 (+ A B),數(shù)據(jù)是統(tǒng)一了,,卻讓人別扭了,,尤其在使用更復(fù)雜的四則混合運(yùn)算時(shí)更讓人難以接受。然而那些 Lisp 擁護(hù)者對這些不能接受 S 表達(dá)式 的人總是持批評鄙視的態(tài)度,。
Lisp 未能成為主流的根本原因是這一語言是反人性的,,它的先進(jìn)是對于機(jī)器的先進(jìn),就像二進(jìn)制對于計(jì)算機(jī)來說是先進(jìn)的一樣,。
人是生物,,對事物的需求都有著多樣性的需求,人類的所有語言對漂亮的形容詞從來不止一個,,對顏色的要求從來就不止黑白亮色,,所以在數(shù)字上選擇了十進(jìn)制而不選擇二進(jìn)制,這是最基本的人性,。Lisp 使用 S 表達(dá)式 抹平了一切多樣性,,禁止人類數(shù)千年來不約而同選擇的的 A+B 這樣的中綴表達(dá)式規(guī)則,違反了人性,,所以受到了廣大開發(fā)者的不接受,。
簡單說,Lisp 語言違反了人類人性中對事物多樣性的需求而不能成為編程語言中的主流,。
為什么 Lisp 沒有流行起來
很久以前,,這種語言站在計(jì)算機(jī)科學(xué)研究的前沿,特別是人工智能的研究方面?,F(xiàn)在,,它很少被用到,這一切并不是因?yàn)楣爬?,類似古老的語言卻被廣泛應(yīng)用,。
其他類似的古老的語言有 FORTRAN、 COBOL,、 LISP,、 BASIC、 和 ALGOL 家族,,這些語言的唯一不同之處在于,,他們?yōu)檎l設(shè)計(jì)。FORTRAN 是為科學(xué)家和工程師設(shè)計(jì)的,,他們在計(jì)算機(jī)上編程的目的是是為了解決問題,。
COBOL 是為了商業(yè)設(shè)計(jì)的,最好的體現(xiàn)在于讓商人們可以利用電腦時(shí)代,。LISP 是了計(jì)算機(jī)科學(xué)研究設(shè)計(jì)的,,最突出的體現(xiàn)在計(jì)算機(jī)基本原理研究。BASIC 是為初學(xué)者設(shè)計(jì)的,。
最后,,ALGOL 語言是有計(jì)算機(jī)程序員修改,演變成其他流行的語言,,如 C,,Pascal 和 Java 的一個龐大的家族。上面提到的某些語言已經(jīng)不像當(dāng)初那么流行了,。我們在這里可以把它們稱作 “失敗”,。
問題是它們?yōu)槭裁词??第一站出來的?COBOL,,很不幸,它以面向商業(yè)人員的很好的可讀性就是它的失敗點(diǎn),。商業(yè)人員發(fā)現(xiàn),,他們可以雇傭程序員去管理他們的系統(tǒng)。程序員自然會偏向于為他們設(shè)計(jì)的語言,,而不是他們的老板,。
所以隨著時(shí)間推移,越來越多的商業(yè)功能都使用例如 VB,,C,,C++ 和 JAVA 實(shí)現(xiàn)了。現(xiàn)在,,只有很少一部分軟件仍通過 COBOL 語言編寫,。BASIC 卻有不同的命運(yùn)。他是為入門人員設(shè)計(jì)的,。那些在微機(jī)上學(xué)習(xí)編程,,他們會使用內(nèi)置的 BASIC 語言作為起點(diǎn)。
隨著時(shí)間推移,,微機(jī)被運(yùn)行微軟操作系統(tǒng)的個人電腦,,或者 MacOS 的蘋果電腦所代替。這種語言逐漸被 VB 所取代,。雖然他是面向初級程序員,,它有一段時(shí)間代替了 COBOL。為什么要耗費(fèi)這么多的資源在昂貴的編譯器上,,而便宜的解釋器在我們的電腦上已經(jīng)存在,?
最近,微軟以遷移到 .NET 框架上,讓 VB 跟在后面,。它的替代者,, C# 就是 ALGOL 家族中的一員,跟 Java 相近,。這些年 FORTRAN 的使用起起伏伏,。在某一階段,差不多所有科學(xué)方面的代碼是用它來寫的,。它的優(yōu)點(diǎn)是這門語言中沒有指針,,并且不允許存在遞歸。
這意味著所有數(shù)據(jù)的引用位置都可以在編譯時(shí)確定,。FORTRAN 編譯器 利用這些額外的信息使程序運(yùn)行格外地迅速,。不幸的是,隨著時(shí)間的推移,,固定大小的數(shù)組這種數(shù)據(jù)結(jié)構(gòu)變得過時(shí)了?,F(xiàn)在,科學(xué)要處理任意形狀的風(fēng)格,,甚至表述更為復(fù)雜的真實(shí)世界,。
這需要在語言中額外地加入指針。這些情況發(fā)生的時(shí)間段里,,F(xiàn)ORTRAN 逐漸走向沒落?,F(xiàn)在,它被轉(zhuǎn)移到高性能計(jì)算工作,,其中新的并行矩陣和矢量運(yùn)算最近添加到這門語言中,,仍然使它擁有性能優(yōu)勢。ALGOL 語言家族取得了成功,。
其原因是,,這些語言是由程序員為程序員寫的。隨著時(shí)間的推移,,這些與系統(tǒng)和應(yīng)用相關(guān)的語言成為了現(xiàn)在最常用的語言,。它的優(yōu)點(diǎn)是越多地程序員使用,這門語言就能得到更多地改進(jìn),,并且越來越多地程序是用它們來寫就的,。這提供了一個良性循環(huán),更多的程序員們又被聘請?jiān)诩壕帉懙某绦蛏瞎ぷ鳌?/p>
這是一個網(wǎng)絡(luò)效應(yīng)的例子,。一個系統(tǒng)的 “價(jià)值” 是它的用戶數(shù)目的平方,,在于以此速率增長的用戶之間的交互作用。那么為什么 Lisp 語言家族會站在失敗者一邊呢,?有些人認(rèn)為是語法的錯,。Lisp 因?yàn)樗睦ㄌ柖裘阎N也⒉徽J(rèn)為是這個理由。許多用戶說良好的格式可以讓他們跟上這些括號,。
同時(shí),,Lisp 語言被發(fā)明不久后,有一個叫 “super-bracket” 的語法可以讓人快速表示出任意數(shù)量的回括號 “ ) ” ,。這個特性在今天已經(jīng)很少有人使用了,。最后,,優(yōu)秀的編輯器解決了大多數(shù)的語法問題,。另一些人經(jīng)常抱怨 Lisp 是一門函數(shù)式語言。這是失敗的理由嗎,?自然,,跟早期的語言相比,只有 Lisp 算是函數(shù)式的,。但事實(shí)上,,我認(rèn)為沒有這么簡單。Lisp 也有命令式語言的特性,,ALGOL 系列語言也可以被當(dāng)作一門純正的函數(shù)式語言來用,。
如果有人想選擇一種特定的編程范式來寫代碼,一些特定的語言可以讓這個選擇更容易的實(shí)現(xiàn),。然而,,現(xiàn)代語言已經(jīng)足夠靈活,它們能支持多種編程范式,,近乎完全命令式的 Lisp 沒有理由不存在,。或許 lisp 的問題在于他使用了垃圾回收,?
在那個時(shí)候,,只有 Lisp 作為計(jì)算機(jī)語言采用了這個特性。誠然,,垃圾回收會占用大量的計(jì)算資源,,而早期計(jì)算機(jī)在該方面的不足足以組織 Lisp 大展拳腳了。但是,,我認(rèn)為這仍然不是主要的原因,。
Lisp 是用來寫那些復(fù)雜度相當(dāng)高的程序的,而這些程序在事實(shí)上都必須帶有一個垃圾回收模塊,,如果你用其他的語言來寫…… 大概很難比 Lisp 實(shí)現(xiàn)的要好吧,?眾所周知的事實(shí)是,任何一個如此復(fù)雜的程序,,如果用其他語言寫的話都不可避免的戴上一個比 Lisp 垃圾回收臃腫不少的功能模塊…… Lisp 的失敗,,恰恰是因?yàn)樗晒Γ@讓他的目標(biāo)變得模糊。Lisp 相對與早期的語言實(shí)在是非常靈活,,靈活到足以改變自身形式以適應(yīng)需求,。對于其他的語言來說,如果想要完成一個龐大的任務(wù),,就需要把這個任務(wù)打碎成一小塊一小塊的然后完成,。
如果是一個更大的呢?甚至連編譯都需要分步完成了,。但是 Lisp 不是這樣的,,由于他強(qiáng)大的能力,程序員可以將 Lisp 改造成特定領(lǐng)域的專門工具 —— 順手的工具將順手的解決問題 —— 任務(wù)輕松完成了,。
由于語言的正交性(譯者注:這里可能應(yīng)該理解為 “自洽” ),,我們改造過的 Lisp 仍然可以使用原有的編譯器,解釋器運(yùn)行,。那么建立特定領(lǐng)域的語言來作為一個問題的解決方案,,它會出現(xiàn)什么問題呢?
結(jié)果是它非常高效,。然而,,這種做法會使語言分化。這導(dǎo)致許多子語言都略有不同,。這是 Lisp 代碼對其他人而言可讀性差的真正原因,。在其他語言中,相對來說比較簡單就能臆測出一段給定代碼的作用,。
有著超強(qiáng)的表達(dá)力的 Lisp,,由于一個給定的符號 (symbol) 可能是一個變量,函數(shù)或操作,,需要閱讀大量代碼才能找出它,。Lisp 失敗的原因是因?yàn)樗乃槠⑶宜乃槠且驗(yàn)槠湔Z言天性與特定領(lǐng)域方案的風(fēng)格造成的,。
而網(wǎng)絡(luò)效應(yīng)則恰恰相反,。越來越少的程序員使用相同的方言,因此它相對與 ALGOL 語言家族的總價(jià)值下降,。如果有人現(xiàn)在設(shè)計(jì)一種語言,,該如何避免這種問題呢?如果語言的表達(dá)性是我們的目標(biāo),,那么它必須以某種方式加以調(diào)整,。
這門語言必須要有特意的限制,來保證所編寫代碼的可讀性,。Python 是一門成功的語言,,它已經(jīng)做到了這些,,其中某些限制是硬編碼的,而另一些則是以約定成俗的方式存在,。不幸的是,,這么久過去了并且發(fā)明了這么多 Lisp 的變種語言,在其之上建立的其它新語言大概并不是所要的答案,。
根本不會有足夠多的用戶使它與眾不同,。也許解決的辦法是,慢慢加入類似 Lisp 的語言功能到 ALGOL 語言家族中,。幸運(yùn)的是,,這似乎是正在發(fā)生的事。新的語言(C#,,D,,Python 等)趨向于擁有垃圾回收機(jī)制。他們也往往比舊的語言更具正交性,。
Lisp 語法反人性中的隱藏寶藏
如果你剛開始學(xué)習(xí)的時(shí)候,主要是忍受著它那前綴表達(dá)式和大量的括號,,去學(xué)習(xí)它的函數(shù)式編程,,這時(shí)候沒覺得 Lisp 有多厲害,等到學(xué)到了 宏(macro)這個主題的時(shí)候,,就被 Lisp 的變態(tài)的能力真正地震撼了:
這不是一門簡單的編程語言,,它是一門創(chuàng)造其他語言的語言。
Lisp 在制造別的語言(DSL)時(shí)是如此的成功,,以至于它最終走向了 “失敗”,。
很多語言解決問題的思路是分而治之:把一個大任務(wù)拆分成一個個小任務(wù),再把小任務(wù)拆分成更小的任務(wù),,然后去編碼實(shí)現(xiàn),。
Lisp 則有著截然不同的思路,由于其強(qiáng)大的能力,,Lisp 程序員傾向于改造 Lisp,,把這門語言改造成一個問題領(lǐng)域相關(guān)的語言,即 DSL ,,然后用這個 DSL 來輕松編程,,去解決問題。
當(dāng)然這個改造的過程是個漸進(jìn)式的:
“在編程的時(shí)候你可能會想 'Lisp 要是有這樣或者那樣的操作符就好了,?!?那你就可以直接去實(shí)現(xiàn)它。之后,,你會意識到使用新的操作符也可以簡化程序中另一部分的設(shè)計(jì),,如此種種,。語言和程序一同演進(jìn)。就像交戰(zhàn)兩國的邊界一樣,,語言和程序的界限不斷地移動,,直到最終沿著山脈和河流確定下來,這也就是你要解決的問題本身的自然邊界,。最后你的程序看起來就好像語言就是為解決它而設(shè)計(jì)的,。并且當(dāng)語言和程序彼此都配合得非常完美時(shí),你得到的將是清晰,、簡短和高效的代碼,。” — Paul Graham 《On Lisp》,。
這種使用 DSL 去解決問題的方法有什么問題呢,?
碎片化! 會出現(xiàn)很多 “小語言”,,這些語言之間有細(xì)微的不同,,這就是為什么你的 Lisp 代碼對別人來說讀起來很吃力的原因。
而其他語言則不存在這個問題,,相對容易去理解代碼的含義,。Lisp, 由于其變態(tài)的表達(dá)能力, 一個符號可能是個變量,,函數(shù),,操作符。你需要花費(fèi)大量時(shí)間去閱讀代碼才能搞清楚它到底是什么含義,,這就太悲催了,。
Lisp 的 “失敗” 的一大原因就是它的碎片化,而碎片化又源于語言本身的特性和它那用 DSL 解決問題的風(fēng)格,。
我在說 “失敗” 的時(shí)候,,一直用引號, Lisp 真的失敗了嗎,?No,!Lisp 的思想已經(jīng)進(jìn)入到了現(xiàn)在主流的語言中,無論是 Python,,JavaScript,,甚至 Java 都具備函數(shù)式編程的能力,還有像 Scala 這樣既能 OOP,,又能 FP 的語言,。 但是,Lisp 那強(qiáng)大的宏,,那運(yùn)行時(shí)改變自身的能力并沒有被其他語言接受,,Ruby 比較接近,,但是差得還很遠(yuǎn)??赡艽蠹液ε逻@個雙刃劍了吧,!
|