王垠談?wù)Z法

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

對于四則運(yùn)算,這些優(yōu)先級(jí)還算簡單。可是一旦有了更多的操作,就容易出現(xiàn)混淆。這就是為什么數(shù)學(xué)(以及邏輯學(xué))的書籍難以看懂。 實(shí)際上,那些看似復(fù)雜的公式,符號(hào),不過是在表示一些程序里的“數(shù)據(jù)結(jié)構(gòu)”,“對象”以及“函數(shù)”。大部分讀數(shù)學(xué)書的時(shí)間,其實(shí)是浪費(fèi)在琢磨這些公式:它們到底要表達(dá)的什么樣一個(gè)“數(shù)據(jù)結(jié)構(gòu)”或者“操作”!這個(gè)“琢磨”的過程,其實(shí)就是程序語言里所謂的“語法分析”(parse)。

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

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

      (D ‘(^ x 2) ‘x)

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

同樣的公式,傳統(tǒng)的數(shù)學(xué)符號(hào)是這個(gè)樣子:

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

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

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

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

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

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

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

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

作為一個(gè)菜鳥級(jí)攝影師,你不覺得第一行中間太“空”了一點(diǎn)嗎?

再來看看S表達(dá)式如何表達(dá)這東西:

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

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

其實(shí)我們還可以更進(jìn)一步。因?yàn)榧^的兩邊全都用括號(hào)括起來了,所以其實(shí)我們并不需要那兩個(gè)箭頭就能區(qū)分“左”和“右”。所以我們可以把它簡化為:

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

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

Lisp 的很多其它的設(shè)計(jì),比如“垃圾回收”,后來被很多現(xiàn)代語言(比如 Java)所借鑒。可是人們遺漏了一個(gè)很重要的東西:Lisp 的語法,其實(shí)才是世界上最好的語法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

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