ClojureScript

1.10.238 版本發佈

2018 年 3 月 26 日
ClojureScript 團隊

ClojureScript 1.10 終於發佈了!

本次更新做了許多增強,包括支援 Java 9 和 Java 10。接下來,我們將快速帶您瀏覽一些主要的新功能。

cljs.main

ClojureScript 現在包含 cljs.main,它將 clojure.main 的許多功能(並透過新的 Clojure CLI 工具普及)帶到 ClojureScript,同時還針對 ClojureScript 環境的額外考量和功能提供特殊支援,允許您直接從命令列編譯或執行 ClojureScript 程式碼。

cljs.main 的功能使我們能夠大幅簡化新用戶的體驗。先前版本的快速入門指南中的許多複雜性和繁瑣步驟都已不復存在。

對於有經驗的 ClojureScript 使用者來說,cljs.main 使原本應該很簡單的操作變得真正簡單。現在許多事情只需一兩個命令列旗標即可完成,無需任何複雜的設定。

您可以在ClojureScript 命令列中閱讀有關 cljs.main 的更多資訊。

共享 AOT 快取

共享 AOT 快取功能會儲存從 JAR 編譯的產物,以便它們可以在您的專案中重複使用,或者在您在專案中進行清理建置時重複使用。這可以避免重新編譯未變更的程式碼,從而節省時間。

您可以在共享 AOT 快取中閱讀有關此功能的更多資訊。

模組處理改進和 Closure 更新

ClojureScript 現在使用最新的 Closure Compiler 版本 (v20180204),其中包含許多用於使用 Node 模組的改進。除了 Closure Compiler 更新之外,ClojureScript 端也實作了一些變更。其中一些改進包括:

  • CommonJS 和 ES6 模組現在可以互相 require

  • Closure 現在可以偵測並移除更多 UMD 包裝器

  • 對於存在 node_modules 目錄但不應使用的情況,可以將 :npm-deps 選項設定為 false 來停用 Node 模組支援

穩定名稱

引入了一個新的編譯器選項,:stable-names。這可以減少進階建置之間的名稱變動,並在您使用:modules時促進正確的供應商化。

增強的 Socket REPL 和 alpha pREPL

此版本新增了幾個 cljs.server.* 命名空間,用於與 -Dclojure.server.repl 整合,從而實現更豐富的 ClojureScript Socket REPL 功能。

此外,還新增了對 pREPL 的支援。這與 Socket REPL 類似,但它是基於 EDN 的,以便工具可以程式化地使用 REPL 輸出。

新的 Socket REPL 和 pREPL 功能需要 Classpath 上有 Clojure 1.10.0-alpha4

core.specs.alpha

core.specs.alpha 函式庫已移植到 ClojureScript,並在此版本中作為選擇加入的功能提供。此函式庫包含描述核心巨集和函式的 Spec。此外,還包含對 ns 特殊形式的支援。

若要使用此函式庫,只需 require 新的 cljs.core.specs.alpha 命名空間。這樣做會註冊 defnlet 和其他巨集的 Spec,並且後續編譯這些巨集時將會進行 Spec 驗證。

以下說明其在 REPL 中的用法。假設您不小心嘗試使用 Clojure 特有的功能來參照函式庫的所有符號,而該功能在 ClojureScript 中不存在。

 cljs.user=> (require '[clojure.set :refer :all])
 clojure.lang.ExceptionInfo: Don't know how to create ISeq from: clojure.lang.Keyword at line 1 ...

這個錯誤有點難以理解。現在,讓我們再次嘗試,但這次使用 core.specs.alpha

cljs.user=> (require 'cljs.core.specs.alpha)
nil
cljs.user=> (require '[clojure.set :refer :all])
clojure.lang.ExceptionInfo: Call to cljs.core/require did not conform to spec:
In: [0 1 :refer] val: :all fails spec: :cljs.core.specs.alpha/refer at:
[:args :spec :libspec :lib+opts :options :refer] predicate: coll?
...

產生的錯誤基本上表示 :all 是問題所在,並且 :refer 採用集合作為其引數。

此功能仍為 alpha 版,但我們鼓勵您嘗試一下並回報您可能發現的任何錯誤!

可簡化的序列產生器

在此 ClojureScript 版本中,iteraterepeatcycle 的結果現在可以直接簡化。這將 Alex Miller 為 Clojure 所做的一些偉大工作,在幾年前帶到了 ClojureScript。這表示當您簡化這些函式的輸出時,將獲得更好的效能。

例如,以一個基準測試為例,當使用 :advanced 優化編譯時,執行 (transduce (take 64) + (iterate inc 0)) 共 10,000 次。您可以在您的機器上嘗試此基準測試,但我們看到在 V8 和 SpiderMonkey 上執行速度快了 4.5 倍,在 JavaScriptCore 上快了 3.3 倍。

此外,這提供了一種處理大型輸出的方法,無需涉及中間序列產生,從而繞過了 ClojureScript 中缺乏局部清除和不可避免的頭部保持問題。這表示您現在可以執行像這樣的程式:

(transduce (comp (map inc) (filter odd?) (take 1e8)) + (iterate inc 0))

它們將消耗非常少的記憶體。此範例在 Node REPL 中大約 10 秒內完成,僅使用數 MB 的 RAM,而先前它基本上永遠不會終止,會消耗數 GB 的 RAM。

Map 項目

為了方便起見,ClojureScript 一直為未排序的持久 map 項目傳回 2 元素向量。對於許多使用案例來說,這是可以的,因為 map 項目可以用作向量。但是,反之則不然,為了實現這一點,ClojureScript 需要為持久向量新增人工支援,以用於 keyval map 項目函式。

為了與 Clojure 對齊,ClojureScript 現在為這種情況傳回專用的 map 項目類型,並取消了人工向量支援。一個說明與 Clojure 更高保真度的範例是,當 empty 應用於 map 項目時,這允許 ClojureScript 正確地傳回 nil。(由於 map 項目正好有兩個元素,因此不可能有空的 map 項目。)

雖然這確實整理了一些事情,但請注意將向量錯誤地視為 map 項目的程式碼。例如,雖然 (key (first {:a 1}))(val (first {:a 1})) 完全有效,但 (key [:a 1])(val [:a 1]) 是不正確的,並且會導致執行階段例外狀況。

最後,使用專用的 map 項目類型可以提高某些使用 map 項目程式碼的效能。例如,在 :advanced 模式下,此程式碼:

(simple-benchmark [m (zipmap (range 100) (range))]
  (reduce (fn [a [k v]] (if (even? v) (+ a k) a)) 0 m) 100000)

在 JavaScriptCore 中執行速度快了 11%,在 V8 中快了 18%,在 SpiderMonkey 中則快了驚人的 105%。如果您使用專用的 keyval 函式而不是解構,V8 的效能會提高到快 44%,而 SpiderMonkey 則提高到 112%。

如需 ClojureScript 1.10.238 中的完整更新清單,請參閱變更

貢獻者

感謝所有為 ClojureScript 1.10.238 做出貢獻的社群成員:

  • Andrea Richiardi

  • Bruce Hauman

  • Dieter Komendera

  • Enzzo Cavallo

  • Erik Assum

  • Hendrik Poernama

  • Jannis Pohlmann

  • Jinseop Kim

  • John Newman

  • Juho Teperi

  • Levi Tan Ong

  • Mark Hepburn

  • Martin Klepsch

  • Mike Fikes

  • Oliver George

  • Paulus Esterhazy

  • Roman Scherer

  • Thomas Heller

  • Tim Pote

  • Tom Mulvaney