王垠談?wù)Z法

使用和研究過這么多程序語言之后,我覺得幾乎不包含多余功能的語言,只有一個:Scheme。所以我覺得它是學習程序設(shè)計最好的入手點和進階工具。當然 Scheme 也有少數(shù)的問題,而且缺少一些我想要的功能,但這些都瑕不掩瑜。在用了很多其它的語言之后,我覺得 Scheme 真的是非常優(yōu)美的語言。

要想指出 Scheme 所有的優(yōu)點,并且跟其它語言比較,恐怕要寫一本書才講的清楚。所以在這篇文章里,我只提其中一個最簡單,卻又幾乎被所有人忽視的方面:語法。

其它的 Lisp “方言”也有跟 Scheme 類似的語法(都是基于“S表達式”),所以在這篇(僅限這篇)文章里我所指出的“Scheme 的優(yōu)點”,其實也可以作用于其它的 Lisp 方言。從現(xiàn)在開始,“Scheme”和“Lisp”這兩個詞基本上含義相同。

我覺得 Scheme (Lisp) 的基于“S表達式”(S-expression)的語法,是世界上最完美的設(shè)計。其實我希望它能更簡單一點,但是在現(xiàn)存的語言中,我沒有找到第二種能與它比美。也許在讀過這篇文章之后,你會發(fā)現(xiàn)這種語法設(shè)計的合理性,已經(jīng)接近理論允許的最大值。

為什么我喜歡這樣一個“全是括號,前綴表達式”的語言呢?這是出于對語言結(jié)構(gòu)本質(zhì)的考慮。其實,我覺得語法是完全不應(yīng)該存在的東西。即使存在,也應(yīng)該非常的簡單。因為語法其實只是對語言的本質(zhì)結(jié)構(gòu),“抽象語法樹”(abstract syntax tree,AST),的一種編碼。一個良好的編碼,應(yīng)該極度簡單,不引起歧義,而且應(yīng)該容易解碼。在程序語言里,這個“解碼”的過程叫做“語法分析”(parse)。

為什么我們卻又需要語法呢?因為受到現(xiàn)有工具(操作系統(tǒng),文本編輯器)的限制,到目前為止,幾乎所有語言的程序都是用字符串的形式存放在文件里的。為了讓字符串能夠表示“樹”這種結(jié)構(gòu),人們才給程序語言設(shè)計了“語法”這種東西。但是人們喜歡耍小聰明,在有了基本的語法之后,他們開始在這上面大做文章,使得簡單的問題變得無比復(fù)雜。

Lisp (Scheme 的前身)是世界上第二老的程序語言。最老的是 Fortran。Fortran 的程序,最早的時候都是用打孔機打在卡片上的,所以它其實是幾乎沒有語法可言的。

顯然,這樣寫程序很痛苦。但是它卻比現(xiàn)代的很多語言有一個優(yōu)點:它沒有歧義,沒有復(fù)雜的 parse 過程。

在 Lisp 誕生的時候,它的設(shè)計者們一下子沒能想出一種好的語法,所以他們決定干脆先用括號把這語法樹的結(jié)構(gòu)全都括起來,一個不漏。等想到更好的語法再換。

自己想一下,如果要表達一顆“樹”,最簡單的編碼方式是什么?就是用括號把每個節(jié)點的“數(shù)據(jù)”和“子節(jié)點”都括起來放在一起。Lisp 的設(shè)計者們就是這樣想的。他們把這種完全用括號括起來的表達式,叫做“S表達式”(S 代表 "symbolic")。這貌似很“粗糙”的設(shè)計,甚至根本談不上“設(shè)計”。奇怪的是,在用過一段時間之后,他們發(fā)現(xiàn)自己已經(jīng)愛上了這個東西,再也不想設(shè)計更加復(fù)雜的語法。于是S表達式就沿用至今。

在使用過 Scheme,Haskell,ML,和常見的 Java,C,C++,Python,Perl,…… 之后,我也驚訝的發(fā)現(xiàn), Scheme 的語法,不但是最簡單,而且是最好看的一個。這不是我情人眼里出西施,而是有一定理論依據(jù)的。

首先,把所有的結(jié)構(gòu)都用括號括起來,輕松地避免了別的語言里面可能發(fā)生的“歧義”。程序員不再需要記憶任何“運算符優(yōu)先級”。

其次,把“操作符”全都放在表達式的最前面,使得基本算術(shù)操作和函數(shù)調(diào)用,在語法上發(fā)生完美的統(tǒng)一,而且使得程序員可以使用幾乎任何符號作為函數(shù)名。

在其他的語言里,函數(shù)調(diào)用看起來像這個樣子:f(1),而算術(shù)操作看起來是這樣:1+2。在 Lisp 里面,函數(shù)調(diào)用看起來是這樣(f 1),而算術(shù)操作看起來也是這樣(+ 1 2)。你發(fā)現(xiàn)有什么共同點嗎?那就是 f 和 + 在位置上的對應(yīng)。實際上,加法在本質(zhì)也是一個函數(shù)。這樣做的好處,不但是突出了加法的這一本質(zhì),而且它讓人可以用跟定義函數(shù)一模一樣的方式,來定義“運算符”!這比起 C++ 的“運算符重載”強大很多,卻又極其簡單。

關(guān)于“前綴表達式”與“中綴表達式”,我有一個很獨到的見解:我覺得“中綴表達式”其實是一種過時的,來源于傳統(tǒng)數(shù)學的歷史遺留產(chǎn)物。幾百年以來,人們都在用 x+y 這樣的符號來表示加法。之所以這樣寫,而不是 (+ x y),是因為在沒有計算機以前,數(shù)學公式都得寫在紙上,寫 x+y 顯然比 (+ x y) 方便簡潔。但是,中綴表達式卻是容易出現(xiàn)歧義的。如果你有多個操作符,比如 1+23。那么它表示的是 (+ 1 ( 2 3)) 呢,還是 (* (+ 1 2) 3)?所以才出現(xiàn)了“運算符優(yōu)先級”這種東西??匆姏]有,S表達式已經(jīng)在這里顯示出它沒有歧義的優(yōu)點。你不需要知道 + 和 * 的優(yōu)先級,就能明白 (+ 1 (* 2 3)) 和 (* (+ 1 2) 3) 的區(qū)別。第一個先乘后加,而第二個先加后乘。

對于四則運算,這些優(yōu)先級還算簡單??墒且坏┯辛烁嗟牟僮鳎腿菀壮霈F(xiàn)混淆。這就是為什么數(shù)學(以及邏輯學)的書籍難以看懂。 實際上,那些看似復(fù)雜的公式,符號,不過是在表示一些程序里的“數(shù)據(jù)結(jié)構(gòu)”,“對象”以及“函數(shù)”。大部分讀數(shù)學書的時間,其實是浪費在琢磨這些公式:它們到底要表達的什么樣一個“數(shù)據(jù)結(jié)構(gòu)”或者“操作”!這個“琢磨”的過程,其實就是程序語言里所謂的“語法分析”(parse)。

這種問題在微積分里面就更加明顯。微積分難學,很大部分原因,就是因為微積分的那些傳統(tǒng)的運算符,其實不是很好的設(shè)計。如果你想了解更好的設(shè)計,可以參考一下 Mathematica 的公式設(shè)計。試試在 Mathematica 里面輸入“單行”的微積分運算(而不使用它傳統(tǒng)的“2D語法”)。

其實 Lisp 已經(jīng)可以輕松地表示這種公式,比如對 x^2 進行微分,可以表示成

      (D ‘(^ x 2) ‘x)

看到了嗎?微分不過是一個用于處理符號的函數(shù) D,輸入一個表達式和另一個符號,輸出一個新的表達式。

同樣的公式,傳統(tǒng)的數(shù)學符號是這個樣子:

這是什么玩意???d 除以 dx,然后乘以 x 的平方?

在 Lisp 里,你其實可以比較輕松地實現(xiàn)符號微分的計算。SICP里貌似有一節(jié)就是教你寫個符號微分程序。做微積分這種無聊的事情,就是應(yīng)該交給電腦去做。總之,這從一方面顯示了,Lisp 的語法其實超越了傳統(tǒng)的數(shù)學。

其實我一直都在想,如果把數(shù)學看成是一種程序語言,它也許就是世界上語法最糟糕的語言。數(shù)學里的“變量”,幾乎總是沒有明確定義的作用域(scope)。也就是說他們只有“全局變量”。上一段話的 x,跟下一段話的 x,經(jīng)常指的不是同一個東西。所以訓練有素的數(shù)學家,總是避免使用同一個符號來表示兩種不同的東西。很快他們就發(fā)現(xiàn)所有的拉丁字母都用光了,于是乎開始用希臘字母。大寫的,小寫的,粗體的,斜體的,花體的,…… 而其實,他們只不過是想實現(xiàn) C++ 里的 “namespace”。

可惜的是,很多程序語言的設(shè)計者沒能擺脫數(shù)學的思想束縛,對數(shù)學和邏輯有盲目崇拜的傾向。所以他們繼續(xù)在新的語言里使用中綴表達法。Haskell,ML,Coq,Agda,這些“超高級”的語言設(shè)計,其實都中了這個圈套。在 Coq 和 Agda 里面,你不但可以使用中綴表達式,還可以定義所謂的 "mixfix" 表達式。這樣其實是把簡單的問題復(fù)雜化。想讓自己看起來像“數(shù)學”,很神秘的樣子,其實是學會了數(shù)學的糟粕,自討苦吃。

最后,從美學的角度上講,S表達式是很美觀的設(shè)計。所有的符號都用括號括起來,這形成一種“流線型”的輪廓。而且由于可以自由的換行排版,你可以輕松地對齊相關(guān)的部分。在 Haskell 里,你經(jīng)常會發(fā)現(xiàn)一些很蹩腳,很難看的地方。這是因為中綴表達式的“操作符”,經(jīng)常不能對在一起。比如,如果你有像這樣一個 case 表達式:

case x
Short _ -> 1
VeryLooooooooooooooooooooooooog _ -> 2

為了美觀,很多 Haskell 程序員喜歡把那兩個箭頭對齊。結(jié)果就成了這樣:

case x
Short _ -> 1
VeryLooooooooooooooooooooooooog _ -> 2

作為一個菜鳥級攝影師,你不覺得第一行中間太“空”了一點嗎?

再來看看S表達式如何表達這東西:

(case x
(-> (Short _) 1)
(-> (VeryLooooooooooooooooooooooooog _) 2))

發(fā)現(xiàn)“操作符總在最前”的好處了嗎?不但容易看清楚,而且容易對齊,而且沒有多余的間隙。

其實我們還可以更進一步。因為箭頭的兩邊全都用括號括起來了,所以其實我們并不需要那兩個箭頭就能區(qū)分“左”和“右”。所以我們可以把它簡化為:

(case x
((Short _) 1)
((VeryLooooooooooooooooooooooooog _) 2))

最后我們發(fā)現(xiàn),這個表達式“進化”成了 Lisp 的 case 表達式。

Lisp 的很多其它的設(shè)計,比如“垃圾回收”,后來被很多現(xiàn)代語言(比如 Java)所借鑒??墒侨藗冞z漏了一個很重要的東西:Lisp 的語法,其實才是世界上最好的語法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 第一部分Common Lisp介紹第1章 介紹一下Lisp你在學的時候覺得已經(jīng)明白了,寫的時候更加確信了解了,教別...
    geoeee閱讀 3,218評論 5 8
  • 3.4 說說相等和內(nèi)部表示 在Lisp中主要有5種相等斷言,因為不是所有的對象被創(chuàng)建的時候都是相等意義上的相等。數(shù)...
    geoeee閱讀 1,969評論 0 6
  • Lisp的本質(zhì) - climbdream的個人空間 - 開源中國社區(qū)https://my.oschina.net/...
    葡萄喃喃囈語閱讀 764評論 0 10
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,511評論 19 139
  • From:http://emacsist.com/10845 點 這里 查看更多 Emacs 相關(guān)推薦文章 或 最...
    海神之奏閱讀 5,783評論 0 26

友情鏈接更多精彩內(nèi)容