在Lisp語言中,macro是一個神器,可以“動態的生成代碼”,然后被執行,這種方式給到Lisp無限的表達能力。除Lisp之外,很少有語言支持Macro這個特性,我記得 GWT之中曾經有一個類似的Generator的概念,可以在編譯期間提供一定的代碼生成能力(GWT Project),很多GWT的高級特性都是通過這個語言特性而得以實現的,譬如:
-
UiBinder 可以實現申明式的 UI 開發。
-
ImageBundle 簡單的實現很多圖片的聚合,從而減少round-trip,提高網頁裝載速度。
Scala自2.10開始嘗試支持macro編程,2.11基本步入”成熟“階段,2.12中基本穩定,不過,在最新的路線圖中,Scala之父,Martin再次表現了將在未來的scala版本(Dotty)中重新設計macro,采用基於scalameta的一套編譯器“中立”的元信息模型,試圖讓macro變得更簡單、容易。因此,嘗試 macro,目前仍然具有很大的風險性,或許隨着scala語言的未來版本,現有的macro也需要重新遷移、實現。
What is Macro?
簡而言之,macro就是讓你的代碼具備有生成代碼的能力,最簡單的macro可以算上 C/C++ 的 ##define, 最早的 C 程序員就廣泛的使用 define 來生成代碼。(嚴格意義上,C的define並不算是 macro)
Why using Macro?
程序員喜歡寫有挑戰的代碼,而肯定不喜歡 Copy & Paster,或者甚至連Copy Paste都算不上的重復性代碼,一個很簡單的例子,就是Java程序員都最熟悉的如下代碼:
public class User {
private String name;
private int age;
public String getName(){ return this.name;}
public void setName(String name){ this.name = name;}
public int getAge(){ return this.age; }
public void setAge(int age){ this.age = age; }
public String toString(){ return "name:" + name + "/age:" + age;} public int hashCode(){ return name.hashCode ^ age;}
public boolean equalus(Object other){ return ......} }
這段代碼是非常有Java風格的一段代碼,每一個Java程序員都很熟悉,這據說是一種設計模式,叫做”JavaBeans”, 這個模式有很多的延續:
-
基本上每個IDE,都會有一個工具來幫你自動的生成 getter, setter, toString, hashCode等工具
-
有一個 Lombok 的開源項目,幫你來生成這些代碼。
-
scala 經常用
case class User(name:String, age: Int)
來鄙視Java -
Kotlin 經常用
data class User(val name: String, val age: Int)
來鄙視Java
這是一個很好的案列:
-
這段代碼的“干貨”很少,用一段為代碼
data class User(val name: String, val age: Int
就可以表述清楚的東西,我們卻需要啰啰嗦嗦的寫一大段代碼。沒有程序員喜歡這種差事,除非,老板真的是按代碼行數付工資的。 -
Lombok可以讓你編寫很少的代碼,他來負責生成剩下的代碼。
-
Eclipse/JetBrains,可以讓你寫少量的代碼,他來負責生成剩下的代碼。
-
Scala/Kotlin這兩個編譯器,可以讓你寫少量的代碼,他來負責生成剩下的代碼。
當我們只需要寫最為”核心“的那一步代碼,而剩下的代碼,完全可以按照一個模式去自動生成時,我們的工作效率就大幅度提升了,而言,Less is more,更少的代碼就意味着更快速的代碼閱讀、評審,更少的BUG,這一切,都會帶給程序員更大的幸福感。
實際上,還有一種方式,也可能是最好的方式,就是利用macro,來幫你生成代碼。當然,要這門語言,支持macro。從目前的情況來看,支持macro的語言還是非常有限的。scala作為學術派的代表,很有幸可以提供macro的能力。
When to using Macro?
記得在一本Clojure的書中說過什么時候可以使用Macro?
-
如果你可以使用普通的函數實現,不要用macro
-
如果你可以用其他方式實現,不要用macro
-
實在找不到辦法,嘗試一下macro。
其實所有的一切,都是在於Macro的復雜性。如果說Clojure代碼有如天書,那么,去生成天書的macro代碼,就更是天書中的天書了。 這個放到Scala中,只會變得更為復雜,原因無二:Scala有一個非常復雜的類型體系,而Macro中你需要和這個無比復雜的類型體系打交道。所以,如果你對Scala的了解程度還不夠深入的話,Macro也會是無從下手的。不過,這也是一個不錯的試金石,如果你想說你對scala的了解程度很好的話,那么不妨挑戰一下Macro。這絕對是一個既復雜,但又超級強大的工具。Macro的強大,有的時候,是無敵的,這些特性,只能在Macro中完成,你沒有更好的被的辦法。
How can Macros Do?
在本篇文章中,我不准備介紹 How to writing Macro?這個我會嘗試在后續的文章中介紹,我會先介紹使用 macro,我們可以做些什么?
-
更有效的日志性能。我們經常告誡,不要讓日志影響性能:
if(logger.isDebugEnabled) logger.debug("message is heavy in business: ");
為什么不能夠直接用如下的代碼來替代呢?logger.debug(message)
,既滿足性能的需求,又簡潔呢? Macro Can
-
編譯時期進行更好的語法檢查。
"\d+".r.test("1234")
復雜的正則表達式,往往需要復雜的調試工作,如果編譯器能幫助我檢查語法錯誤,相信可以讓代碼開發變得更為簡單。其實,除了正則表達式,我們還可以檢查原代碼中的日期常量、時間常量等,當然,如果在做一下優化,自動的將復雜的字符串常量預編譯為構造值,可能還會獲得更好的性能。Macro Can
-
是否可以在編譯時,對我們的SQL語句,自動的進行語法檢查?更好的,鏈接到一個數據庫,進行預處理檢查,看看是否有錯誤的表名、字段名拼寫?是否有語法性的錯誤?早期的4gl語言,就有這個特性,還是很實用的。
Macro Can -
為我們的數據對象(Case ClassData Class)自動生成 toJSON, toXML, toProtobuf, toThrift等工具方法。反射是一種方式,所以有fast-son, fast-xml等框架?有沒有可能有比fast-jsonfast-xml更加fast的方式呢?
有的,Macro Can -
還能干什么?更為一般的,如果你要實現功能抽象成最小化的一個Core,然后你告訴一個程序員,如何實現剩下的功能,例如,如何編寫gettersettertoString方法,讓他來幫你實現代碼。那么,你可以用 Macro 來直接完成,不再需要一個程序員的工作。這個也算是 AI 的一個近似值了把。
Macro can write code for You
結合Scala的implicit,macro有很強大的功能,我會結合我的開源框架 scala-sql 應用的 macro 特性,給你繼續Scala的神奇Macro之旅。
參考: