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 模块现在可以互相引用

  • Closure 现在能够检测并删除更多 UMD 包装器

  • 可以通过将 :npm-deps 选项设置为 false 来禁用 Node 模块支持,这种情况适用于 node_modules 目录存在但不应该使用的情况

稳定名称

引入了一个新的编译器选项 :stable-names。这减少了高级构建之间的名称变化,并在您使用 :modules 时促进适当的供应商化。

增强的套接字 REPL 和 alpha pREPL

此版本为与 -Dclojure.server.repl 集成添加了一些 cljs.server.* 命名空间,允许更丰富的 ClojureScript 套接字 REPL 功能。

此外,还添加了对 pREPL 的支持。这与套接字 REPL 类似,但它基于 EDN,以便工具可以以编程方式使用 REPL 输出。

新的套接字 REPL 和 pREPL 功能要求 Clojure 1.10.0-alpha4 在类路径中。

core.specs.alpha

core.specs.alpha 库已移植到 ClojureScript,在本版本中作为可选功能提供。该库包含描述核心宏和函数的规范。还包括对 ns 特殊形式的支持。

要使用此库,只需引用新的 cljs.core.specs.alpha 命名空间。通过这样做,将为 defnlet 和其他宏注册规范,随后对这些宏的编译将受到规范验证的约束。

以下说明了它在 REPL 中的使用。假设您不小心尝试引用库中的 *所有* 符号,使用 ClojureScript 中不存在的 Clojure 特定功能

 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。这意味着在规约这些函数的输出时,您将获得更好的性能。

例如,一个基准测试涉及运行 (transduce (take 64) + (iterate inc 0)) 共 10,000 次,并在 :advanced 优化下编译。您可以在自己的机器上尝试此基准测试,但我们看到在 V8 和 SpiderMonkey 上运行速度快了 4.5 倍,在 JavaScriptCore 上快了 3.3 倍。

此外,这提供了一种在不涉及中间序列生成的情况下处理大量输出的方法,从而绕过了 ClojureScript 中缺少局部变量清除和不可避免的头保持问题。这意味着您现在可以运行像这样的程序

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

它们将消耗很少的内存。此示例在 Node REPL 中使用大约几兆字节的 RAM 完成,大约需要 10 秒,而以前它基本上永远不会终止,消耗数十亿字节的 RAM。

映射条目

为了方便起见,ClojureScript 始终为未排序的持久映射条目返回 2 元素向量。对于许多用例来说,这没问题,因为映射条目可以用作向量。但反过来则不行,为了实现这一点,ClojureScript 需要为 keyval 映射条目函数添加对持久向量的模拟支持。

为了与 Clojure 保持一致,ClojureScript 现在为此情况返回专用映射条目类型,并消除了模拟向量支持。一个说明与 Clojure 保持更高保真度的示例是,这使得 ClojureScript 能够在将 empty 应用于映射条目时正确返回 nil。(因为映射条目正好有两个元素,所以不可能存在空映射条目。)

虽然这确实简化了一些事情,但请注意处理向量作为映射条目的代码。例如,虽然 (key (first {:a 1}))(val (first {:a 1})) 完全有效,但 (key [:a 1])(val [:a 1]) 是错误的,会导致运行时异常。

最后,使用专用映射条目类型可以在处理映射条目的某些代码中带来性能提升。例如,在 :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