不要輕易使用超程式設計
超程式設計就像核彈,自己夢寐以求,卻不希望別人擁有。
一般說超程式設計分為兩類,一類是巨集,在編譯時期生成程式碼;另一類是執行時修改程式碼的行為。而不論是哪一類,我的建議是在決定使用之前要慎重考慮。超程式設計能讓我們擴充套件語言本身,是十足的黑魔法;但用好不易,容易造成團隊/社群在意見是實現上的分裂。(另外這篇文章裡主要是對超程式設計的一些吐糟,並不包含基礎知識的介紹。)
#光明與黑暗
一上來,我們先看 Common Lisp 裡的 loop
巨集就。一方面,它體現了巨集的強大;另一方面,它展現了巨集能給我們帶來的複雜。
熟悉 C/Java 語言都知道迴圈是語言本身提供的關鍵字,一般是 for
。但 Lisp 語言特別精簡,它認為迴圈只是遞迴的一個特殊形式,語言本身也不包含任何的迴圈關鍵字。於是有人用巨集實現了 loop
(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 ...
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.
Swift 表示不希望用巨集來解決語言本身的缺陷。
而我的理解是當我們希望用巨集(或其它超程式設計手段)時,很可能是我們使用的語言缺少了某些特性,例如 Java 的 lombok 提供的 @Getter/@Setter
等註解,就是因為 Java
沒有相應的語言層面的支援,看看 Kotlin 的支援你就會明白的。
但即便有了巨集(或超程式設計)的支援,你有信心能做出讓整個團隊都信服的設計嗎?如果沒有,最好還是慎重為之。
#寫在最後
雖然是吐糟,但這篇之間重寫了三次。想表達的內容很多,最終還是把其它的東西刪去, Lisp curse 還是我想真正表達的東西吧,其它的基礎知識,有緣人自然會從其它地方學會。
年輕人容易崇拜力量,我們也別忘了陽光還有影子。