在《JavaScript : The Good Parts》,Douglas Crockford談到「JavaScript是披著C外衣的Lisp」,而在《Coder at Work》中,諸位軟體大師時不時地就會提到Lisp,同時,在談論函數式程式設計的文件書籍中,經常也會看到Lisp這語言。

為什麼是Lisp?一門繼Fortran之後最古老的語言!

函數式設計的始祖?

在《駭客與畫家》的〈書呆的復仇〉中,Paul Graham談到,如果依序看看Java、Perl、Python、Ruby等程式語言,就會注意到一個有趣的模式,它們越來越像Lisp。從表面看來,這些語言或多或少地,都具備有一級函式的概念。

回頭看看過去十年來的程式語言發展,無論是既有的語言,或者是新的語言,一級函式都會是考量在內的特性,由於應用程式需求的日趨複雜,為了追求更高階的抽象能力,函數式的設計典範也在近十年來熱門了起來,想要徹底瞭解函數式設計,學習純函數式語言(例如Haskell)是個不錯的選擇,回過頭來,對現在許多具備函數式色彩的語言或框架,在應用上都會有極大的啟發。

然而,如果想要追溯函數式設計的始祖,最古老的莫過於1958年被創造出來的Lisp了。在常見的程式語言中,算術表達式通常採用中序表示法,例如,一加二會寫成1+2,然而,在Lisp中會寫成(+ 1 2),許多文件都會提到,就像是前序表達式。這種寫法看來奇怪,實際上,如果有個add函式來處理一加二,呼叫時就會是(add 1 2),在Lisp中,+就是個函式,而整個Lisp程式,都是由(+ 1 2) 的單一表示法來構成。

這樣的表示法稱為S表達式(s-expression),如果是(7-1)/(- 4 2)呢?Lisp會寫成(/ (- 7 1) (- 4 2)),乍看不習慣,將/與-都看成是函式呼叫,就可以理解了。相較於常見的語言,會有中序表達式、函式呼叫、逗號、分號、大括號等語法,然而想要理解Lisp,就只要掌握S表達式就可以了。

也因此某些程度上,想要學習Lisp並不困難,一個不錯的資源是《ANSI Common Lisp》(https://goo.gl/VNBuka),只要看完它的第二章,就能掌握足夠多的Common Lisp基礎知識,若想要直接快速掌握一些實用範例,可以看看《Practical Common Lisp》(https://goo.gl/vMW4Tm)第三章的簡單資料庫程式範例,在看完之後,實際上也會發現,Lisp本身並不那麼強調純函數式,那只是它在表述某些情況時,自然展現出來的特性。

現代語言的思想

回過頭來看看現代語言好了,需要具備有哪些特性,才能被稱為一門現代語言呢?在〈書呆的復仇〉中談到,Lisp在剛誕生的時候,就包括了九個新的概念,像是條件構造、函式型態、遞迴、動態定型、垃圾回收、由表達式組成整個程式、符號(Symbol)型態、使用符號與常數組成的樹來作為程式碼表示、無論是哪個時期,都可以使用整個語言等。

如果接觸過JavaScript、Python、Ruby等語言,條件構造、函式型態、遞迴、動態定型、垃圾回收等,對開發者來說,不會算是什麼新特性,如果對於函數式設計也有一定程度的概念,學習Lisp應該不會有太多的障礙(也許得先習慣那堆括號),或許還會覺得,除了S表達式很酷之外,某些程度上,在這些特性上,並沒感覺到太多的驚艷。

現代語言中,少有由表達式組成整個程式的,以往也偶而可見「別叫開發者區分陳述句與表達式」之類的笑話,然而,像Scala中,if...else、for甚至是try之類的語法結構,也能是個表達式的話,在某些場合就能派上用場。就我對語言的接觸經驗來說,Haskell是最接近由表達式組成整個程式的語言,而帶來的表述能力也極為強大。

符號型態對多數開發者可能比較陌生,它不是變數,也不是字串,而是代表程式中會使用到的符號。而最接近Lisp符號概念的,大概是Ruby的Symbol型態,可用來引用Ruby中的物件,像是數字、方法、類別、模組等。

也許這些概念,對開發者都不陌生,不過要想想,Lisp竟然是從1958年開始,就具備這類概念,也就是這近六十年來,一些語言只是不斷將Lisp的特性,間接以各自的語法實現罷了。Paul Graham談到之所以會如此,是因為Lisp以數學作為基礎,而後不斷提升運行速度,而Fortran之後的主流語言,基礎是建立在硬體架構上,將運作速度作為設計的出發點,而後再讓語言一步步變得強大。

你在建立自己的語言嗎?

如果要在寫一個應用程式之前,先創造一個可以寫這個應用程式的語言,你會有什麼反應呢?怎麼可能?然而,實際上,開發者某些程度常這麼做,只不過,是在既有的語言基礎上進行,講白了,就是設計程式庫或框架,特別是有些語言不希望你做出它語法限制之外的事情之時,對這類限制性高的語言來說,如果設計程式庫或框架都很難達到目的的話,另一個可能性就是尋求黏合其他語言來做這件事了。

Java就是一個代表,Java 8有Lambda了,想在既有語言上建立小型語言,現在情況好了一些,若還有更進一步需求,就是藉助Groovy了,然而在過去誰擔任Groovy這類的角色呢?讓人回想起框架中充斥著XML的年代。

XML?我們不是在談論Lisp嗎?意外地,XML與Lisp之間的關係相當有趣,在〈The Nature of Lisp〉(https://goo.gl/Nb9bDh)中,Slava Akhmechet談到,XML與Lisp的概念有極大的相似之處,XML可以用來表示資料,也可以用來表示函式結構,進一步地,還可以用來作為程式碼的通用儲式格式,也就是程式碼就是一種資料(Code is also always data)。

如果接觸過meta-programming就會知道,對這方面的支援越是強大的語言,程式碼作為資料的概念就越鮮明,在這類語言中,往往也將基於原語言建立小型語言的能力,也就是建立內部DSL的能力彰顯出來,像是Ruby、Scala這類語言就是個例子。

在Lisp中,一級函式也不過是程式碼作為資料的一種方式,實際上,整個Lisp程式就是Lisp資料結構(基於S表達式),因此,開發者可以修補Lisp,甚至建立一個完全不同於Lisp的語言,也就因此,歷史上,Lisp衍生出許多的方言,像是Scheme,Brendan Eich在設計JavaScript時,曾經借鑑了C的語法及Scheme語言中函式的概念,無怪乎Douglas Crockford會說:「JavaScript是披著C外衣的Lisp」。

體會可程式的程式語言風格

在《Practical Common Lisp》中談到,最接近Lisp思想的格言是「可程式的程式語言(the programmable programming language)」,似乎是強調了Lisp在meta-programming方面的強大能力,然而實際上,這代表的是另一種開發方法。也就是《ANSI Common Lisp》中〈1.3 New Approach〉談到的,「設法儘量降低錯誤的成本,而不是希望人們不犯錯。錯誤的成本是修補它所花費的時間。使用強大的語言跟好的開發環境,這種成本會大幅地降低。」

文件中也談到,Lisp風格從1960年代一直朝著這個方向演進。如果Lisp離你太遠,那麼想想JavaScript。在Brendan Eich為《Effective JavaScript》寫的序中就談到,短短十天要完成JavaScript的設計,他的解決方式就是,讓JavaScript具有非常大的可塑性,允許開發者修補這門語言。

Lisp值得學習嗎?這取決於個人!這也許是個沒什麼建設性的答案,然而事實就是如此,我接觸過許多程式語言,然後探索了Lisp,其中大多數的概念,我在其他語言中都有接觸過了,因而並沒有太大的進入障礙,然而,反過來說,如果想認識現代語言的發展趨向,從中獲得啟發,又不想從許多語言中零落地學習那些特性,那麼直接看看Lisp,可以是個不錯的捷徑,將來學習其他現代語言,也會是一種助力!

作者簡介


Advertisement

更多 iThome相關內容