cljs.user=> (require '[clojure.set :refer :all])
clojure.lang.ExceptionInfo: Don't know how to create ISeq from: clojure.lang.Keyword at line 1 ...
2018 年 3 月 26 日
ClojureScript 團隊
ClojureScript 1.10 終於發佈了!
本次更新做了許多增強,包括支援 Java 9 和 Java 10。接下來,我們將快速帶您瀏覽一些主要的新功能。
ClojureScript 現在包含 cljs.main
,它將 clojure.main
的許多功能(並透過新的 Clojure CLI 工具普及)帶到 ClojureScript,同時還針對 ClojureScript 環境的額外考量和功能提供特殊支援,允許您直接從命令列編譯或執行 ClojureScript 程式碼。
cljs.main
的功能使我們能夠大幅簡化新用戶的體驗。先前版本的快速入門指南中的許多複雜性和繁瑣步驟都已不復存在。
對於有經驗的 ClojureScript 使用者來說,cljs.main
使原本應該很簡單的操作變得真正簡單。現在許多事情只需一兩個命令列旗標即可完成,無需任何複雜的設定。
您可以在ClojureScript 命令列中閱讀有關 cljs.main
的更多資訊。
共享 AOT 快取功能會儲存從 JAR 編譯的產物,以便它們可以在您的專案中重複使用,或者在您在專案中進行清理建置時重複使用。這可以避免重新編譯未變更的程式碼,從而節省時間。
您可以在共享 AOT 快取中閱讀有關此功能的更多資訊。
ClojureScript 現在使用最新的 Closure Compiler 版本 (v20180204),其中包含許多用於使用 Node 模組的改進。除了 Closure Compiler 更新之外,ClojureScript 端也實作了一些變更。其中一些改進包括:
CommonJS 和 ES6 模組現在可以互相 require
Closure 現在可以偵測並移除更多 UMD 包裝器
對於存在 node_modules
目錄但不應使用的情況,可以將 :npm-deps
選項設定為 false
來停用 Node 模組支援
引入了一個新的編譯器選項,:stable-names
。這可以減少進階建置之間的名稱變動,並在您使用:modules
時促進正確的供應商化。
此版本新增了幾個 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 函式庫已移植到 ClojureScript,並在此版本中作為選擇加入的功能提供。此函式庫包含描述核心巨集和函式的 Spec。此外,還包含對 ns
特殊形式的支援。
若要使用此函式庫,只需 require 新的 cljs.core.specs.alpha
命名空間。這樣做會註冊 defn
、let
和其他巨集的 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 版本中,iterate
、repeat
和 cycle
的結果現在可以直接簡化。這將 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。
為了方便起見,ClojureScript 一直為未排序的持久 map 項目傳回 2 元素向量。對於許多使用案例來說,這是可以的,因為 map 項目可以用作向量。但是,反之則不然,為了實現這一點,ClojureScript 需要為持久向量新增人工支援,以用於 key
和 val
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%。如果您使用專用的 key
和 val
函式而不是解構,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