Wednesday, February 15, 2017

推荐 clojure 語言 --- simple, concise, consistent

最初是看了 Paul Graham 推荐 Lisp ,然後工作上因為 Riemann monitoring tool 是用 clojure (Lisp 的一種 dialect) 寫的,一時興起就決定來研究這個傳說中「駭客的秘密武器」的語言家族。學習的過程就是重複、交替做三件事:
  1. 讀 clojure 的線上書籍、文件,寫語言的特性、語言的觀念 ---重點關注在 clojure 的文章。
  2. 寫一些不痛不癢的小習題。 4clojure.com 有 150 多題的練習題。
  3. 讀一些和 clojure 相關、又不直接談 clojure 的文章。比方說「教 Lisp 的文章」「Lisp 的歷史」「Rich Hickey 的一些 slide 」
後來研究了所謂的 language oriented programming, bottom-up approach 等等應用 macro 來做 meta-programming 的高級應用技巧。 當我和朋友聊這個的時候,朋友給了一個回應:「嗯,也許 Lisp 語言真的是最適合做 meta-programming 的語言,但是你上一回寫 meta-programming 是什麼時候?」

於是,我心裡的大哉問就變成了:「對於自身不太使用 meta-programming 技巧的程式設計師,clojure 語言是否依然可以帶來生產力的提昇?」會問這個問題是因為之前有看過一篇文章講為什麼「函數式編程」可以提高程式設計師的生產力,文章中所論述最重要的理由,因為「高階函數」與「惰性求值」這兩項特性可以提高程式碼模塊化的程度。函數式編程的特性,現在許多的語言都加入了,那 clojure 如果不討論 meta-programming 的話,它的表達能力 (expressiveness) 還會比一些更主流的語言 python, Ruby 等也有函數編程特性的語言來得更強嗎?

那我們來看一段程式碼吧: flatten 函數,可以用來「壓平」樹狀資料結構。
例如:
(flatten [1 2 [3 [4 5] 6]])
(1 2 3 4 5 6)
接下來,有趣的事就是,如果我們去看 flatten 的 source code 。

啥! flatten 居然是用 tree-seq 來定義的!為了這件事,我還多看了一下,clojure 哪些內建的函數也是用 tree-seq 來定義? 結果 file-seq 和 xml-seq 也都是用 tree-seq 來定義。而 tree-seq 是一個高階函數,接受三個參數:一個是用來接收「定義分支的函數」、一個是用來接收「展開分支子樹的函數」,一個是用來接收「樹資料結構」。

「樹」這樣子的資料結構、對應的「走訪方式」,例如: tree-seq。在其它的語言,往往沒有「極簡」的實現。像 python 的話,自然也會有「樹」相關的走訪函式庫、「樹」的資料結構實現。卻比較多是物件導向式的、或是每一家發展不同風格的作法。這一類的問題:標準函式庫較為一致、外部函式庫較不一致的問題,我認為是語言表達能力受限制的主因之一。

要深入討論語言表現能力的話,我認為這關系到程式語言設計的一致性 (consistency)。以 python 為例, python 在函數式編程領域,後期發展的語法傾向是用 list comprehension 來取代 filter, map, reduce 之類的函數。這個很自然,因為用 list comprehension 的表現能力更強,寫 python 的使用者可以在記憶最少的語法的前提之下,得到最大的表達能力。

於是這就回到了一個很有趣的議題,程式語言的設計,其實必須考慮人類認知能力的限制。以 C++ 語言為例,C 的語法、語件導向的語法、template 的語法,三種語法都大不相同。tempalte 尤其複雜。複雜的語法導致了使用者學習時難學、使用者上手之後也難用,因為語法冗長。而 Lisp 的括號語法,最初來自於偷懶,因為 Lisp 的作者還沒有為它設計合適的語法,所以就先用語法樹 (syntax tree) 來湊合著用,卻因此讓 Lisp 語言成為了有最簡單 (simple) 語法的語言,同時也是語法最一致 (consistent)的語言,進而可以讓使用者寫出簡潔 (concise) 的程式。