2024 Clojure 状态调查 中分享您的想法!

欢迎!请参阅 关于 页面以获取更多关于此信息。

0 投票
ClojureScript
CLJS 编译器将为在 {{let}} 内(以及在某些情况下在 {{let}} 外)创建的函数生成大量的不必要的函数包装器。只有在 {{loop}} 内或函数使用 {{recur}} 来处理 JS {{var}} 的怪癖时,这些函数包装器才是必要的。据我所知,此提交 [1] 改变了行为,使其始终如此。

这可能会生成大量难以消除的“额外”代码。

一个示例说明,即使这些包装函数并没有使用任何 Local,这些包装函数可能仍然具有相当长的参数列表。

(ns test.app)

(defn other [x])

(defn dummy [{:strs [foo bar coll] :as p}]
  (let [x "test"
        y "test"
        z "test"

        a (fn [i] i)
        b (fn [i] i)
        c (fn [i] i)

    (fn return []
      (array a b c))
    ))


生成的代码

// 由 ClojureScript 1.10.520 编译 {}
goog.provide('test.app');
goog.require('cljs.core');
test.app.other = function test$app$other(x) {
    return null;
};
test.app.dummy = function test$app$dummy(p__526) {
    var map__527 = p__526;
    var map__527__$1 = (!(map__527 == null)
    ? map__527.cljs$lang$protocol_mask$partition0$ & 64 ||
      cljs.core.PROTOCOL_SENTINEL === map__527.cljs$core$ISeq$
        ? true
        : false
    : false)
        ? cljs.core.apply.call(null, cljs.core.hash_map, map__527)
        : map__527;
    var p = map__527__$1;
    var foo = cljs.core.get.call(null, map__527__$1, 'foo');
    var bar = cljs.core.get.call(null, map__527__$1, 'bar');
    var coll = cljs.core.get.call(null, map__527__$1, 'coll');
    var x = 'test';
    var y = 'test';
    var z = 'test';
        // 这可以只是一个 var a = function(i) { return i };
    var a = (function(x, y, z, map__527, map__527__$1, p, foo, bar, coll) {
        return function(i) {
            return i;
        };
    })(x, y, z, map__527, map__527__$1, p, foo, bar, coll);
    var b = (function(x, y, z, a, map__527, map__527__$1, p, foo, bar, coll) {
        return function(i) {
            return i;
        };
    })(x, y, z, a, map__527, map__527__$1, p, foo, bar, coll);
    var c = (function(x, y, z, a, b, map__527, map__527__$1, p, foo, bar, coll) {
        return function(i) {
            return i;
        };
    })(x, y, z, a, b, map__527, map__527__$1, p, foo, bar, coll);
    return (function(x, y, z, a, b, c, map__527, map__527__$1, p, foo, bar, coll) {
        return function test$app$dummy_$_return() {
            return [a, b, c];
        };
    })(x, y, z, a, b, c, map__527, map__527__$1, p, foo, bar, coll);
};

。...map文件路径...


我还没有进行任何基准测试,但我觉得这确实有一定的运行时开销。

导致这种情况的代码应该优化,以仅在实际需要时发出这些包装器,并且只为确实使用的局部变量。

或者,我们可以使用 JavaScript {{const}}(或 {{let}}),因为现在使用的大多数浏览器(>98%)都支持这个特性 [2],并且 Closure Compiler 将根据某些 {{:language-out}} 设置为我们生成这些函数包装器(即,当前默认设置)。
 

[1] https://github.com/clojure/clojurescript/commit/78d20eebbbad17d476fdce04f2afd7489a507df7
[2] https://caniuse.cn/#feat=const

14 个答案

0 投票

评论由: thheller

在这个使用宏生成一些后续要使用的函数的实际项目中添加另一个数据点。

优化后的代码看起来像这样

`
ii(
ia,
Y,
(function() {

return function(X, m) {
  var r = ji(oh);
  m = Bh(m[0], X);
  li(X, r, Ng, null, "odd");
  mi(r, m);
  return [[r], [r, m]];
};

})(na, Y, ia, G, K, O, T, l, n, p, q, v, x, D),
(function() {

return function(X, m, r, t) {
  return ni(X, m, 1, r[0], t[0]);
};

})(na, Y, ia, G, K, O, T, l, n, p, q, v, x, D)
);
`

,大小为403字节,如果删除这些包装器,则可以减少到233字节。我们可以看到 Closure 已经删除了函数绑定,但未删除立即执行函数表达式 (IIFE),所以我们可以完全消除这些包装器。

`
ii(
ia,
Y,
function(X, m) {

var r = ji(oh);
m = Bh(m[0], X);
li(X, r, Ng, null, "odd");
mi(r, m);
return [[r], [r, m]];

},
function(X, m, r, t) {

return ni(X, m, 1, r[0], t[0]);

}
);
`

我怀疑这实际上可以导致实际应用中的代码量相当大。

我会看看下个星期是否能解决这个问题。

0 投票

评论由: dnolen

我喜欢使用 JavaScript let 作为绑定并将这作为任务让 Closure 处理。这将消除在 ClojureScript 中自行处理此问题的许多自定义代码。

0 投票
评论时间为:thheller

因为一些评论在迁移过程中丢失了,这里做一个简要回顾。

该补丁导致 cljs-canary 的 core.async 和 cljs-oops 构建失败。可以忽略 cljs-oops。

https://github.com/cljs-oss/canary/tree/results/reports/2019/05/17/job-000933-1.10.529-2c0eada6

我无法确定 core.async 中发生的事情。查看代码后,我觉得它应该总是导致 node 进程崩溃。我不明白为什么它在 REPL 中可以工作,但无论是否使用该补丁,以及是否通过常规编译或手动运行 node,我都可以可靠地重复出现崩溃。

在 core.async master 仓库,使用 CLJS master 运行时,我得到以下失败信息

$ clojure -Sdeps '{:paths ["src/test/cljs" "src/main/clojure"] :deps {org.clojure/clojurescript {:git/url "https://github.com/clojure/clojurescript.git :sha "3a0c07477ae781bf521bdc2b074ed7b783bb93f3"}}}' -m cljs.main -d out -re node -c cljs.core.async.test-runner

$ node out/main.js  
正在测试 cljs.core.async.pipeline-test

正在测试 cljs.core.async.buffer-tests

正在测试 cljs.core.async.timers-test

正在测试 cljs.core.async.tests

/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.js:3376
}})();
    ^
错误:断言失败:此异常是预期的
false
    at /mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.cljs:112:9
    at /mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.cljs:112:9
    at cljs$core$async$tests$state_machine__18890__auto____1 (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.js:3376:4)
    at cljs$core$async$tests$state_machine__18890__auto__ (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.js:3392:62)
    at cljs$core$async$impl$ioc_helpers$run_state_machine (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/impl/ioc_helpers.cljs:35:23)
    at cljs$core$async$impl$ioc_helpers$run_state_machine_wrapped (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/impl/ioc_helpers.cljs:39:6)
    at /mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.cljs:112:9
    at Immediate.cljs$core$async$impl$dispatch$process_messages (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/impl/dispatch.cljs:18:7)
    at runCallback (timers.js:705:18)
    at tryOnImmediate (timers.js:676:5)


使用我修改后的代码仓库并应用了补丁,我得到了不同的堆栈跟踪(预期的)和相同的失败信息

$ clojure -Sdeps '{:paths ["src/test/cljs" "src/main/clojure"] :deps {org.clojure/clojurescript {:git/url "https://github.com/thheller/clojurescript.git :sha "b83dcb00546d570ce74fcf130a70a82326857323"}}}' -m cljs.main -d out -re node -c cljs.core.async.test-runner  

$ node out/main.js  

正在测试 cljs.core.async.pipeline-test

正在测试 cljs.core.async.buffer-tests

正在测试 cljs.core.async.timers-test

正在测试 cljs.core.async.tests

/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.cljs:130
        (go
        ^
错误:断言失败:此异常是预期的
false
    at switch__20288__auto__ (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.cljs:130:9)
    at /mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.cljs:130:9
    at cljs$core$async$tests$state_machine__20289__auto____1 (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.js:4103:4)
    at cljs$core$async$tests$state_machine__20289__auto__ (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.js:4119:62)
    at cljs$core$async$impl$ioc_helpers$run_state_machine (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/impl/ioc_helpers.cljs:35:23)
    at cljs$core$async$impl$ioc_helpers$run_state_machine_wrapped (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/impl/ioc_helpers.cljs:39:6)
    at /mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.cljs:130:9
    at Immediate.cljs$core$async$impl$dispatch$process_messages (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/impl/dispatch.cljs:18:7)
    at runCallback (timers.js:705:18)
    at tryOnImmediate (timers.js:676:5)


仍在试图找出为什么在 REPL 中不会失败,但我认为补丁不是这里问题的真正原因。
0 投票
评论人:mfikes

以下是我运行它的方式


clojure -Sdeps '{:paths ["src/test/cljs" "src/main/clojure"] :deps {org.clojure/clojurescript {:git/url "https://github.com/clojure/clojurescript.git :sha "3a0c07477ae781bf521bdc2b074ed7b783bb93f3"}}}' -m cljs.main -d out -re node src/test/cljs/cljs/core/async/test_runner.cljs


输出如下


$ clojure -Sdeps '{:paths ["src/test/cljs" "src/main/clojure"] :deps {org.clojure/clojurescript {:git/url "https://github.com/clojure/clojurescript.git :sha "3a0c07477ae781bf521bdc2b074ed7b783bb93f3"}}}' -m cljs.main -d out -re node src/test/cljs/cljs/core/async/test_runner.cljs

正在测试 cljs.core.async.pipeline-test

正在测试 cljs.core.async.buffer-tests

正在测试 cljs.core.async.timers-test

正在测试 cljs.core.async.tests
错误:断言失败:此异常是预期的
false
    at /Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3318:19
    at /Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3355:51
    at cljs$core$async$tests$state_machine__18881__auto____1 (/Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3376:4)
    at cljs$core$async$tests$state_machine__18881__auto__ (/Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3392:62)
    at cljs$core$async$impl$ioc_helpers$run_state_machine (/Users/mfikes/Projects/core.async/out/cljs/core/async/impl/ioc_helpers.js:96:74)
    at cljs$core$async$impl$ioc_helpers$run_state_machine_wrapped (/Users/mfikes/Projects/core.async/out/cljs/core/async/impl/ioc_helpers.js:99:63)
    at /Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3407:67
    at Immediate.cljs$core$async$impl$dispatch$process_messages (/Users/mfikes/Projects/core.async/out/cljs/core/async/impl/dispatch.js:20:9)
    at processImmediate (internal/timers.js:443:21)
    at process.topLevelDomainCallback (domain.js:136:23)
错误:断言失败:此异常是预期的
false
    at /Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3923:19
    at /Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3961:51
    at cljs$core$async$tests$state_machine__18881__auto____1 (/Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3982:4)
    at cljs$core$async$tests$state_machine__18881__auto__ (/Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3998:62)
    at cljs$core$async$impl$ioc_helpers$run_state_machine (/Users/mfikes/Projects/core.async/out/cljs/core/async/impl/ioc_helpers.js:96:74)
    at cljs$core$async$impl$ioc_helpers$run_state_machine_wrapped (/Users/mfikes/Projects/core.async/out/cljs/core/async/impl/ioc_helpers.js:99:63)
    at /Users/mfikes/Projects/core.async/out/cljs/core/async.js:4290:67
    at /Users/mfikes/Projects/core.async/out/cljs/core/async.js:609:13
    at /Users/mfikes/Projects/core.async/out/cljs/core/async/impl/channels.js:201:12
    at Immediate.cljs$core$async$impl$dispatch$process_messages (/Users/mfikes/Projects/core.async/out/cljs/core/async/impl/dispatch.js:20:9)

运行了44个包含200个断言的测试。
0个失败,0个错误。
0 投票

评论由: thheller

(链接:~mfikes):是的,这就是我所说的在REPL中运行事物的方式。你的方式带有补丁时可以工作,但没有补丁时不行。然而,将节点进程与编译分开运行在两种情况下都失败了。据我理解,你的方式是通过管理的节点-repl进程来运行代码。

我无法理解为什么你的方式可以工作,所以我尝试分别运行事物,这样我就可以运行带有{{node --inspect-brk out/main.js}}的过程并附加调试器。然而,由于它总是以这种方式失败,我认为我的补丁实际上并不是出问题的地方。调试像这样的异步代码很难...

0 投票

评论人:mfikes

(链接:~thheller) 可能只有当在REPL中时才工作,因为CLJS-2780中存在延迟的节点关闭代码。

0 投票

评论由: thheller

我理解{{node}}是这样的:任何未被捕获的异常都应该导致node进程(链接:https://node.org.cn/api/process.html#process_event_uncaughtexception 文本:退出)。这似乎在REPL中得到了阻止,因为node-repl.js代码使用了已废弃的“domain”包(链接:https://github.com/clojure/clojurescript/blob/59997385d85e7e1af1559d599eb51fdb1d7e93b1/src/main/clojure/cljs/repl/node_repl.js#L50 文本:捕获)这个错误,从而阻止退出。

我不明白这与我的补丁有什么关系。

在您的堆栈跟踪中,它从domain.js开始,而我的不是。由于负责这项工作代码根本不是CLJS编译代码,所以它们之间应该没有差异,所以我想不出为什么会这样。

`
;; 没有补丁

at Immediate.cljs$core$async$impl$dispatch$process_messages (/Users/mfikes/Projects/core.async/out/cljs/core/async/impl/dispatch.js:20:9)
at processImmediate (internal/timers.js:443:21)
at process.topLevelDomainCallback (domain.js:136:23)

;; 有补丁

at Immediate.cljs$core$async$impl$dispatch$process_messages (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/impl/dispatch.cljs:18:7)
at runCallback (timers.js:705:18)
at tryOnImmediate (timers.js:676:5)

`

无论如何,这似乎表明补丁实际上并不是导致这些失败的原因,因为即使在不带那个REPL错误捕获的情况下运行也会失败。

0 投票

评论人:mfikes

如果您要在其{{project.clj}}中更新{{core.async}}的依赖关系

  • clojure 1.10.0
  • lein-cljsbuild 1.1.7

然后按照{{core.async}}的README中描述的方法(涉及在浏览器中运行它们)运行单元测试,它们通过ClojureScript 1.10.520,但从未完成(显示通过/失败的测试数量),使用CLJS-3077.patch构建的ClojureScript master版本。

0 投票

评论由: thheller

谢谢迈克。我终于弄懂了……逻辑假设所有的let绑定都不需要捕获。然而,逻辑假设可以很容易地判断何时确实在循环中,这是通过检测第一个循环绑定来实现的。它没有考虑到没有绑定的情况……

`
(defn foo [a b]
(let [x ...] ;; 无捕获

(loop [] ;; sneaky
  (let [y ...]  ;; y needs to be captured but wasn't
    (side-effect (fn [] (use x y)))
    (recur))))

`

0 投票

评论由: thheller

新补丁应该更加可靠(并且通过核心异步测试)。我还添加了一些测试,尽管它们实际上有点脏,但我想不出更干净的方式来测试这一点。

0 投票

评论人:mfikes

CLJS-3077-2.patch 通过 CI 和 Canary (/)
(由于生成的代码更改,预期 {{cljs-oops}} 故障。)

0 投票

评论人:mfikes

CLJS-3077-2.patch 已添加到 Patch Tender (i)

0 投票

评论人:mfikes

CLJS-3077-2.patch 不再适用。

0 投票
参考: https://clojure.atlassian.net/browse/CLJS-3077 (由 thheller 报告)
...