關於語言的思考
之前寫了那麼多 Haskell 的不好的地方,卻沒有提到它好的地方。其實我必須承認,我從 Haskell 身上學到了非常重要的東西,那就是對於“型別”的思考。雖然 Haskell 的型別系統有過於強烈的約束性,從一種“哲學”的角度看感覺“不自然”,但如果一個程式設計師從來沒學過 Haskell,那麼他的腦子裡就會缺少一種重要的東西。這種東西很難從除 Haskell,ML,Coq,Agda 以外的其它語言身上學到。
Haskell 教會我的東西
一個沒有學過 Haskell 的 Scheme 程式設計師,最容易犯的一個錯誤就是,把除#f
(Scheme 的邏輯“假”) 以外的任何值都作為 #t
(Scheme 的邏輯“真”)。很多人認為這是 Scheme 的一個“特性”,可是殊不知這其實是 Scheme 的極少數缺點之一。如果你瞭解 Lisp 的歷史,就會發現在最早的時候,Lisp 把 nil
(空連結串列)這個值作為“假”來使用,而把 nil
意外的其它值都當成“真”。這帶來了邏輯思維的混亂。
Scheme 對 Lisp 的這種混亂做法採取了一定的改進,所以在 Scheme 裡面,空連結串列'()
和邏輯“假”值 #f
被劃分開來。這是很顯然的事情,一個是連結串列,一個是bool
,怎麼能混為一談。Lisp 的這個錯誤影響到了很多其它的語言,比如 C 語言。C 語言把 0 作為“假”,而把不是 0 的值全都作為“真”。所以你就看到有些自作聰明的 C 程式設計師寫出這樣的程式碼:
int i = 0; ... ... if (i++) { ...}
Scheme 停止把 nil
作為“假”,卻仍然把不是 #f
的值全都作為“真”。Scheme 的崇拜者一般都告訴你,這樣做的好處是,你可以使用
(or x y z ...)
這樣的表示式,如果其中有一個不是 #f
,那麼這個表示式會直接返回它實際的值,而不只是 #t
。
然而他們沒有看到的是,其實這個表示式所要達到的“目的”,其實有更加簡單而直接的方法,而不需要把非 #f
的值都作為“真”。你只需要定義一個函式:
(define orf (lambda (ls) (cond [(null? ls) #f] [else (let ([v (car ls)]) (if (not (eq? v #f)) v (orf (cdr ls))))])))
之後你就可以這樣呼叫它:(orf '(#f #f 0 #f "foo"))
。這會在遇到 0 的時候返回它,因為0是這個連結串列裡第一個不是 #f
的值。如果連結串列裡全都是 #f
它就返回 #f
。
這比起 Scheme 的 or
來,不但效率一樣,而且還有一個好處。那就是這個 orf
是一個函式,而 or
是一個巨集。所以你沒法把 or
作為引數傳遞給另一個函式。你沒法使用像 (map or ...)
這樣的寫法。而這個 orf
由於是一個函式,所以可以被作為值,任意的傳遞給另一個函式。
所以雖然我看清楚了 Haskell 的缺點,我不想再使用它,我對它的程式設計師的高傲態度也感到厭倦,然而我的腦子裡卻留下了它教會我的東西。對 Haskell 的理解,讓我成為了一個更好的 Scheme 程式設計師,更好的 Java 程式設計師,更好的 C++ 程式設計師,甚至更好的 shell 指令碼程式設計師。我能夠在任何語言裡再現 Haskell 的程式設計方式的精髓。
所以怎麼說呢,我覺得每個程式設計師的生命中都至少應該有幾個月在靜心學習 Haskell。學會 Haskell 就像吃幾天素食一樣。每天吃素食顯然會缺乏某些營養,但是每天都吃葷的話,你就永遠意識不到身體裡的毒素有多嚴重。
專攻一門語言的害處
我曾經對人說 C++ 裡面其實有一些好東西,但是我沒有說的是,C++ 裡面的壞東西實在太多了。
有些人從小寫 C++,一輩子都在寫 C++。這樣的結果是,他們對 C++ 裡面的“珍珠”掌握的非常牢靠,以至於出現了一種“腦殘”的現象——他們沒法再寫出邏輯清晰的程式。(這裡“珍珠”是一個特殊的術語,它並不含有讚美的意思。請參考這篇博文。)
比如,很多 C++ 程式設計師很精通 functor 的寫法,可是其實 functor 只是由於 C++ 沒有 first-class function 而造成的“變通”。C++ 的 functor 永遠也不可能像 Scheme 的 lambda 函式一樣好用。因為每次需要一個 functor 你都得定義一個新的 class,然後製造這個 class 的物件。如果函式裡面有自由變數,那麼這些自由變數必須通過建構函式放進 functor 的 field 裡面,這樣當 functor 內部的“主方法”被呼叫的時候,它才知道自由變數的值。所以為此,你又得定義一些 field。麻煩了這麼久,你得到的其實不過是 Scheme 程式設計師用起來就像呼吸空氣一樣的 lambda。
這些“精通” functor 的 C++ 程式設計師,認為會用 functor 就說明自己水平高。殊不知 functor 這東西不但是一個“變通”,而且是從函式式語言裡面“學”過來的。在最早的時候,C++ 程式設計師其實是不知道 functor 這東西的。如果你考一下古就會發現,C++ 誕生於 1983 年,而 Scheme 誕生於 1975 年,Lisp 誕生於 1958 年。Scheme 比 C++ 的出現整整早了8年,然而 Scheme 一開始就有 lexical scoping 的 lambda。functor 只不過是對 lambda 的一種繞著彎的模仿。實際上 C++ 後來加進去的一些東西(包括 boost 庫),基本上都是東施效顰。
記得2011年11月11日的良辰吉日,C++ 的創造者 Bjarne Stroustrup 在 Indiana 大學做了一個演講,主題是關於 C++11 的新特性。當時我也在場,主持人 Andrew 是 boost 庫的首席設計師之一(他後來有段時間當過我的導師)。他連誇 Stroustrup 會選日子,只是遺憾演講時間沒有定在11點。
雖然我對 Stroustrup 的幽默感和謙虛的態度感到敬佩,但我也看出來 C++11 相對於像 Scheme 這樣的語言,其實沒有什麼真正的“新東西”。大部分時候它是在改掉自己的一些壞毛病,然後向其它的語言學習一些東西。其實到最後,它仍然不可能達到其他語言那麼原汁原味的效果。然而,由於 C++ 的普及程度之高,現成的程式碼之多,它的地位和重要性還是一時難以動搖的。所以“先輩的罪”,我們恐怕要用很多代人的工作才能彌補。
那麼 C++ 有什麼其他語言沒有的好東西呢?我還是下次再講吧。
多學幾種語言
我今天想說其實就是,沒有任何一種語言值得你用畢生的精力去“精通”它。每個人都應該學習多種語言,這樣才不至於讓自己的思想受到單一語言的約束,而沒法接受新的,更加先進的思想。這就像每個人都應該學會至少一門外語一樣,否則你就深陷於自己民族的思維方式。有時候這種民族傳統的思想會讓你深陷無須有的痛苦,卻無法自拔。
相關文章
- 王垠:關於程式語言的思考
- 關於函數語言程式設計的思考(1)函數程式設計
- 關於函數語言程式設計的思考(2)函數程式設計
- 天天灌水,來寫點關於程式語言的思考。
- 易語言關於微信收款監控軟體寫法的思考
- 關於C語言的常量C語言
- 關於Ruby的語言特點
- 關於C語言的位運算子C語言
- Go 語言關於 Type Assertions 的 坑Go
- 關於C語言的面試問題C語言面試
- 關於中國人自己的程式語言!
- 關於大數(C語言)C語言
- 關於面試的思考面試
- 關於Ioc的思考
- 關於 vs code 中文語言包的 bug
- 關於C語言的簡單介紹C語言
- 關於C語言書的書名徵集C語言
- C語言關於檔案操作的命令C語言
- C語言關於多原始檔的呼叫C語言
- 關於難點的思考
- 關於“開源”的思考
- 關於ETL工具的思考
- 關於c語言輸入字串的總結C語言字串
- 有幾點關於C語言的疑問C語言
- C語言關於標頭檔案的使用C語言
- 關於語義化 HTML 以及前端架構的一點思考HTML前端架構
- 關於中介軟體的思考
- 關於限流實現的思考
- 關於寫部落格的思考
- 關於Fork和Malloc的思考
- 關於Flux,Vuex,Redux的思考VueRedux
- 關於測試流程的思考
- 關於前端的思考與感悟前端
- 關於技術分享的思考
- 關於創業的思考薦創業
- 關於產品的若干思考
- 關於工廠模式的思考模式
- 關於python語言,其他的應用你知道嗎?Python