Vert.x上的Clojure語言支持(項目已開源)

Previous in 白木城全棧:在Vert.x上實現Clojure語言支持的探索

前一篇文章探索了如何在Vert.x上實現Clojure語言包的支持,基本上做了一次可行性分析,最後發現,好像,似乎是可以的,於是我們說干就干,經過大概一周左右的各種攻堅,算是有了一個還算不錯的結果^_^

先說結論,子項目名稱是vertx-lang-clojure,基本上跟Vert.x其他的語言包命名保持一致,已經開源在Github上,地址是:whitewoodcity/vertx-lang-clojure all issues, comments, prs, forks are welcomed.

以下是整個過程的嘮叨,Vert.x的語言支持在v2時代是通過人工,每一個語言一個介面一個介面實現過去,這樣做的結果就是慢,不僅慢,而且效率低,維護成本高,會出現每一個語言特定的錯誤,每一個語言都要專門test一遍,對開發不利,所以在v3時代,項目組轉入了通過使用mvel自動生成代碼的做法,先通過Java將核心代碼實現,然後暴露出相應的API,再通過annotation對每一個api做標記,然後通過mvel的模版功能,將這些api轉換成相應語言的API,這就是vertx-codegen子項目。

通過這種方式,第一批生成的是腳本,也就是面向過程(pp)也就是不需要強制封裝的三個語言的api,分別是Groovy,Ruby和Javascript,因為不需要封裝,做起來最快,唯一需要處理的就是把caseCamel駝峰命名法改成case_snake的命名方式,腳本多數都採用_分割的case_snake命名法;其次就是把Ceylon,Scala還有Kotlin給轉換過去,這三個語言跟Java的衝突都沒有那麼明顯,因為都是面向對象(oop)語言,在封裝層面上,都要求封裝成對象,至少都允許封裝成對象,kt&sc還允許最高層次(top/1st level)是函數,但是這兩個並不衝突,不需要對原有系統做多少改變即可實現,可專註於封裝之後的API生成工作,而且這三個語言做的層次都有些不太一樣,Kotlin的封裝最淺,幾乎用的都是Java自己的API,其次是Ceylon,通過codegen自動生成了一批Ceylon的API,Scala封裝得最為深入,不僅通過codegen自動生成了相對應的API,還自己實現了一層符合Scala使用習慣的API。

那我們知道,三種比較主流的編程範式(pp,oop和fp),Vert.x還缺少函數式編程(fp)的支持,於是我們去Haskell/eta-lang.org項目下面建議,希望作者將eta-lang.org也提供Vert.x的支持,經過群眾們的踴躍投票,在紐約的作者將Vert.x的支持納入了eta v0.2的計劃之中,所以在不遠的將來,我們有可能看到Haskell出現在Vert.x上;那函數式的兩大流派,lisp&haskell,腫么能少了lisp呢?更何況在Vert.x V2的時候曾經出現了Clojure的語言支持,於是我們找到了原作者Toby,諮詢了相關的問題,了解了情況之後,於是我們就琢磨,如何將Clojure搬到Vert.x上去,也算是為eta-lang.org探探路,算是一個練手吧。

具體的可行性分析見前一篇文章,那這裡說一下實現過程,首先是通過上手mvel模版以及codegen來實現代碼自動生成,畢竟是開源的項目,所以官方文檔很不完整,也不友好,好在經過我們不屑努力,總算搞明白了邏輯,原來是需要maven,用maven的compile命令便可自動生成代碼,官方提供了一個starter項目,介紹了如何自動生成md文件,經過一番努力之後,將其成功執行,執行成功之後,就開始通過mvel的模版以及codegen提供的API,來自動生成Clojure代碼,要特別感謝Clojure-Kit這個好用的插件,使得我們可以很方便滴在生成代碼之後,就能看出生成的代碼是否正確。

官方的API也大概分為兩塊,class&dataobject,class主要是動作的包裝,裡面的API都是方法,dataobject則是名詞的包裝,對應的都是data object,json之類的實體,那purely fp語言,封裝的層面都是方法,top/1st level一律都是方法,方法之間的對象是immutable的,以減少副作用,所以第一步先把class裡面的方法轉換成命名空間也就是namespace(ns)裡面的方法,注意Clojure裡面的ns不是Java裡面的class,兩回事,你可以認為ns裡面的都是靜態方法,但是除了Java以外,其他語言幾乎沒有靜態這種概念,靜態是Java為了purely oop而做出的一種妥協和犧牲,作為purely fp語言,則沒有這個考慮,方法就是方法,不存在有靜態的說法,而且方法和對象之間,沒有必然的隸屬關係,不像Java,每一個方法都一定要對應一個類或者是對象,先找到類和對象,才能使用相對應的方法。而且頂層幾乎都是方法,ns是方法的集合,而我們需要轉換的是Java的API,所以第一步就是將所有的非靜態方法和靜態方法都做成方法,靜態方法處理相對簡單,生成的ns直接對應class名稱,然後靜態的方法名照搬,參數照搬,因為clj沒有類型聲明,或者不是強制類型聲明,所以類型可以不用考慮,省不少事,這點跟腳本很像。但是非靜態方法,則要費點事,那就加多第一個參數,第一個參數就是對應的對象名,例如object.methodName() -> (method-name object),同時加多構造器,只不過構造器不再是new,而是new-instance,靈感來自Class.forName().newInstance。

通過這種方式生成代碼,clj類型相對容易處理,但是比較麻煩的是clj的命名,clj的命名是所有語言中需要轉換最多次的一個,比如腳本只需要考慮case_snake便可,而其他的語言,則多數都使用駝峰命名法,所以幾乎不需要什麼轉換,但是clj既使用了case_snake,在文件名上,要兼容Java的文件名,否則出錯,又使用了case-kebab,因為lisp的傳統就是case-kebab,所以費了兩天時間在這種命名的轉換上……

改造完成之後,下一步就是如何將其整合進Verticle的事了,因為ns文件都是方法的集合,不存在有對象這種文件,不能像oop語言一樣,做一個文件,然後實例化這個文件,這不夠fp,所以我們還是通過Java實現一個wrapperverticle,然後在這個Verticle內,調用ns裡面的方法,完成各種函數的註冊,嗯,對,就是Vert.x裡面的各種handler,ns裡面都是方法,Vert.x啟動的時候就是將handler也就是我們寫的lambda註冊到各個源上去,所以我們實現了一個ClojureVerticle.java的包裝類,然後通過ClojureVerticleFactory.java將服務掛到Vert.x上去,原理跟script engine類似,跟Latte作者交流時候,因為之前跟他聊過jsr223的支持,所以這次他一下就明白了,經過這兩個類和META-INF裡面設置之後,vertx對象就能自動識別clj的前綴和後綴啦,那用戶只需要提供給ClojureFactory以ns名(以.clj結尾),ClojureFactory就會自動生成一個ClojureVerticle對象,在該對象的start方法中,就會自動尋找相對應ns文件裡面的start方法,然後將其執行。

但是這樣做又遇到了一個問題,Clojure允許即時編譯執行,其效果等同於腳本解釋執行,也就是提供給Clojure環境一個.clj源文件,Clojure就能夠直接將其編譯並執行,不需要像Java一樣預編譯成.class文件,再執行,這似乎是一件好事,但是,Clojure的並發編譯會出並發問題,所以系統必需先判斷.clj源文件是否存在,如果存在,則加多一個鎖,強迫所有的需要編譯的clj文件的編譯執行串列,嗯,好繞啊,那醬紫肯定是不好啦,腫么辦呢?

嗯,好消息是,clj還可以預編譯嘛,於是找到了clj的maven plugin,將其放入,哇,該插件很好一點就是會自動尋找src/main/clojure以及src/test/clojure目錄下的文件,將其編譯成.class,但是壞處是會把源文件一起拷貝過去,而Clojure的runtime看到.clj結尾的源文件就興奮,會編譯執行,而不是直接執行class文件,所以我們有必要將其排除在外,所以最後的插件變成了醬紫:

<packaging>clojure</packaging>n...n <plugin>n <groupId>com.theoryinpractise</groupId>n <artifactId>clojure-maven-plugin</artifactId>n <version>${clojure.plugin.version}</version>n <extensions>true</extensions>n <configuration>n <copiedNamespaces>n <copiedNamespace>!.*</copiedNamespace>n </copiedNamespaces>n </configuration>n </plugin>n

關鍵的copiedNamespace設置就會將.clj排除在外。

還遇到了codegen項目本身的一些問題,都反饋給了Vert.x官方,經過各種踩坑之後,算是有了一個不錯的結果,項目所對應的jar包已經release在github上了,有興趣的可以下載下來,然後在本地將其引入依賴,就可以像其他語言的依賴一樣使用啦。

後續的工作主要是將現有的ns文件做整合,做成幾個大的ns文件供用戶更加方便地使用,還有文檔,測試等工作,算是一個長期的工作了,但是這個語言的支持,大概的骨架什麼已經出來了,在這裡踩個腳印,算是留念一下^_^

最後截圖一張,左邊是clj文件,中間是其中一個剛寫好的json的ns,右邊是maven的菜單,Clojure的太極圖標真是畫得不錯:

Vert.x官方主頁上的Hello from Vert.x!的clj版的例子,用了箭頭,但是注意clj裡面的箭頭跟其他語言的lambda的箭頭完全兩回事,clj的箭頭是宏:

Hello from Vert.x!

目前代碼總量大概是14萬行+,多數是系統通過模版自動生成的。

snapshot版本已經發布:Index of /repositories/snapshots/com/w2v4

bye


推薦閱讀:

在 GitHub 自己開源的小項目上放一個 donate按鈕,是一種怎樣的體驗?
有哪些開源的集成電路EDA工具?
什麼是開源,當前中國公眾對開源有哪些常見的誤解?

TAG:Clojure | 开源项目 | 函数式编程 |