1. 程式人生 > >不要輕易使用超程式設計

不要輕易使用超程式設計

超程式設計就像核彈,自己夢寐以求,卻不希望別人擁有。

一般說超程式設計分為兩類,一類是巨集,在編譯時期生成程式碼;另一類是執行時修改程式碼的行為。而不論是哪一類,我的建議是在決定使用之前要慎重考慮。超程式設計能讓我們擴充套件語言本身,是十足的黑魔法;但用好不易,容易造成團隊/社群在意見是實現上的分裂。(另外這篇文章裡主要是對超程式設計的一些吐糟,並不包含基礎知識的介紹。)

#光明與黑暗

一上來,我們先看 Common Lisp 裡的 loop 巨集就。一方面,它體現了巨集的強大;另一方面,它展現了巨集能給我們帶來的複雜。

熟悉 C/Java 語言都知道迴圈是語言本身提供的關鍵字,一般是 for。但 Lisp 語言特別精簡,它認為迴圈只是遞迴的一個特殊形式,語言本身也不包含任何的迴圈關鍵字。於是有人用巨集實現了 loop

,它讓我們能以近乎英語的方式在 Lisp 裡寫迴圈語句,這裡從 這裡 摘抄一個例子:

(loop for x in '(a b c d e)      for y from 1      if (> y 1)      do (format t ", ~A" x)      else do (format t "~A" x)      )

你不需要了解這段程式碼的含義,重要的是瞭解像 for .. in .., if ... do ... else ... do 這樣的語法並不是 Lisp 提供的,而是 loop 巨集實現的,這些語法離開了 loop 也就不再合法。

我們看到 loop 巨集讓我們能在 Lisp 語言不支援的情況下享受到近乎現代語言中才包含的 for ... in ...

語法。要知道在 Java 中有兩種 for 語句:

for (int i=0; i < array.length; i++) {    System.out.println("Element: " + array[i]);}for (String element : array) {    System.out.println("Element: " + element);}

而第二種直到 JDK 1.5 才加入。在這之前,廣大的 Java 程式設計師即使已經認識到了第二種寫法的優越性,卻也只能無奈等到語言支援才行。而 Lisp 程式設計師很快就能通過巨集來實現自己理想中的語法。

然而光明與黑暗共生,巨集給我們帶來極大自由的同時,也意味著分裂。每個程式設計師心中理想的語法各不相同,這就意味著一千個程式設計師會有一千種語法。在 Lisp 中巨集是非常容易編寫的(不代表容易正確編寫),意味著真的會存在一千種語法,大家誰也不服誰,因此造成分裂;但在 C/Java 中,沒有巨集的支援,雖然有一千種想法,但大家都寫不出編譯器,於是只能集中討論,統一語法了,再靠大牛們實現了。

而現實就是如此,Common Lisp 嘗試標準化 Lisp,但依舊有人不認同這種理念,例如 Scheme,Common Lisp 標準化的 loop 巨集在 Scheme 中就被拋棄了。

#照進現實

前車之鑑,後事之師。Lisp 強大的功能,反面導致了語言的分裂,最終使 Lisp 也慢慢退出歷史舞臺(主流地位),這也被稱為 The Lisp Curse。而現實中我們也常常會被超程式設計的強大和便捷誘惑,我認為使用超程式設計之前最好考慮會不會造成更多的分裂。最基本的就是不應該自己造語法(DSL)。

當然,我的出發點是多人團隊,較大的專案,考慮的是整體的發展。如果是個人學習,或者小團隊等,超程式設計或許能成為你出眾的祕密武器。但大的專案講求的是合作,DSL 造成的分裂實在是得不償失,尤其是作者離開後,維護的工作經常後繼無人。

近兩年接觸到的 rust 也是提供了巨集的支援,雖然不像 Lisp 巨集一樣容易編寫,但從功能的角度上依舊特別強大,而且模板巨集寫起來也很容易,於是有人想寫一個類似 Python 的 dict 語法

let x = dict!(    "hello" => "world"    ,"hello2" => "world2");

但我個人並不喜歡這種語法,我認為 Clojure 似的語法更簡潔 dict!("hello": "world")。那在團隊裡引入這兩個巨集就會引起程式碼的分裂,後來人在看程式碼時就會很困惑。不利於團隊的建設。

最後分享在 知乎 上看到的引用:

Hygienic Macros and Compile-Time Evaluation: A first-class macro system, or support for compile-time code execution in general, is something we may consider in future releases. We don’t want the existence of a macro system to be a workaround that reduces the incentive for making the core language great.

https://github.com/apple/swift-evolution/tree/104cdde1c374a95a7eaf4768960578db3b9971b7

Swift 表示不希望用巨集來解決語言本身的缺陷。

而我的理解是當我們希望用巨集(或其它超程式設計手段)時,很可能是我們使用的語言缺少了某些特性,例如 Java 的 lombok 提供的 @Getter/@Setter 等註解,就是因為 Java 沒有相應的語言層面的支援,看看 Kotlin 的支援你就會明白的。

但即便有了巨集(或超程式設計)的支援,你有信心能做出讓整個團隊都信服的設計嗎?如果沒有,最好還是慎重為之。

#寫在最後

雖然是吐糟,但這篇之間重寫了三次。想表達的內容很多,最終還是把其它的東西刪去, Lisp curse 還是我想真正表達的東西吧,其它的基礎知識,有緣人自然會從其它地方學會。

年輕人容易崇拜力量,我們也別忘了陽光還有影子。