2024 年 Clojure 状态调查!中分享你的想法。

欢迎!请参阅关于页面以获取此功能的一些更多信息。

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

这可能会生成大量“额外”代码,而 Closure Compiler 无法删除这些代码。

一个假设的示例显示了即使这些包装函数没有使用它们旨在保留的任何局部变量,它们的参数列表也可以变得相当长。

(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);
};

//# sourceMappingURL=app.js.map


尚未进行任何基准测试,但我很确定这也会带来一定的运行时开销。

导致这一现象的代码应该被优化,仅在实际需要时生成那些包装器,并且只针对实际使用的局部变量。

或者我们可以使用JavaScript的const(或let),因为如今98%以上的浏览器都支持这个功能[2],Closure编译器也可以根据特定的: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本身,因此我们可以从这些包装器中得到 absolutely-nothing。

`
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

自从一些评论在迁移过程中丢失了,这里简要回顾一下。

这个补丁导致core.async和cljs-oops在cljs-canary中构建失败。可以忽略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。

在运行CLJS master的core.async 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
    在/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.cljs:112:9
    在/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.cljs:112:9
    在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)
    在cljs$core$async$tests$state_machine__18890__auto__ (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.js:3392:62)
    在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)
    在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)
    在/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.cljs:112:9
    在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)
    在runCallback (timers.js:705:18)
    在tryOnImmediate (timers.js:676:5)


使用应用了补丁的Forked仓库,我得到了不同的调用栈(预期如此)但失败的原因相同

$ 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
    在switch__20288__auto__ (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.cljs:130:9)
    在/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.cljs:130:9
    在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)
    在cljs$core$async$tests$state_machine__20289__auto__ (/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.js:4119:62)
    在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)
    在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)
    在/mnt/c/Users/thheller/code/oss/core.async/out/cljs/core/async/tests.cljs:130:9
    在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)
    在runCallback (timers.js:705:18)
    在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
    在 /用户/mfikes/项目/核心异步/out/cljs/core/async/tests.js:3318:19出问题
    在 /用户/mfikes/项目/核心异步/out/cljs/core/async/tests.js:3355:51出问题
    在 cljs$core$async$tests$state_machine__18881__auto____1 (/用户/mfikes/项目/核心异步/out/cljs/core/async/tests.js:3376:4) 出问题
    在 cljs$core$async$tests$state_machine__18881__auto__ (/用户/mfikes/项目/核心异步/out/cljs/core/async/tests.js:3392:62) 出问题
    在 cljs$core$async$impl$ioc_helpers$run_state_machine (/用户/mfikes/项目/核心异步/out/cljs/core/async/impl/ioc_helpers.js:96:74) 出问题
    在 cljs$core$async$impl$ioc_helpers$run_state_machine_wrapped (/用户/mfikes/项目/核心异步/out/cljs/core/async/impl/ioc_helpers.js:99:63) 出问题
    在 /用户/mfikes/项目/核心异步/out/cljs/core/async/tests.js:3407:67出问题
    在 Immediate.cljs$core$async$impl$dispatch$process_messages (/用户/mfikes/项目/核心异步/out/cljs/core/async/impl/dispatch.js:20:9) 出问题
    在 processImmediate (内部/定时器.js:443:21) 出问题
    在 process.topLevelDomainCallback (域.js:136:23) 出问题
错误:断言失败:这个异常是预期的
false
    在 /用户/mfikes/项目/核心异步/out/cljs/core/async/tests.js:3923:19出问题
    在 /用户/mfikes/项目/核心异步/out/cljs/core/async/tests.js:3961:51出问题
    在 cljs$core$async$tests$state_machine__18881__auto____1 (/用户/mfikes/项目/核心异步/out/cljs/core/async/tests.js:3982:4) 出问题
    在 cljs$core$async$tests$state_machine__18881__auto__ (/用户/mfikes/项目/核心异步/out/cljs/core/async/tests.js:3998:62) 出问题
    在 cljs$core$async$impl$ioc_helpers$run_state_machine (/用户/mfikes/项目/核心异步/out/cljs/core/async/impl/ioc_helpers.js:96:74) 出问题
    在 cljs$core$async$impl$ioc_helpers$run_state_machine_wrapped (/用户/mfikes/项目/核心异步/out/cljs/core/async/impl/ioc_helpers.js:99:63) 出问题
    在 /用户/mfikes/项目/核心异步/out/cljs/core/async.js:4290:67出问题
    在 /用户/mfikes/项目/核心异步/out/cljs/core/async.js:609:13出问题
    在 /用户/mfikes/项目/核心异步/out/cljs/core/async/impl/channels.js:201:12出问题
    在 Immediate.cljs$core$async$impl$dispatch$process_messages (/用户/mfikes/项目/核心异步/out/cljs/core/async/impl/dispatch.js:20:9) 出问题

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

评论者:thheller

(链接: ~mfikes): 是的,这就是我说的在REPL中运行事情。你的方法在没有打补丁的情况下可以工作,但打上补丁就不行了。但是,无论是哪种方式,与编译分开运行Node进程总会失败。据我所知,你的方法是通过一个受管理的node-repl进程来运行代码。

我无法搞清楚为什么你的方法会起效,所以我尝试分别运行事情,这样我可以通过运行具有 {{node --inspect-brk out/main.js}} 的进程来附加调试器。然而,由于这种方法总是失败,我认为我的补丁实际上并不在此处有问题。调试这种异步代码很难...

0投票

评论由:mfikes制作

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

0投票

评论者:thheller

我对{{node}}的理解是,任何未捕获的异常都应导致-node进程退出(链接: https://node.org.cn/api/process.html#process_event_uncaughtexception 文本:退出)。但在REPL中,似乎这种情况被阻止了,因为node-repl.js代码使用了已被弃用的“域”包(链接: 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投票
by

评论由: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投票
by

评论者:thheller

感谢 Mike。我终于明白了...逻辑假设所有循环外的 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投票
by

评论者:thheller

新的补丁应该更可靠(并可通过 core.async 测试)。我还添加了一些测试,虽然承认有点脏,但我找不到更干净的方式来测试这个。

0投票
by

评论由:mfikes制作

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

0投票
by

评论由:mfikes制作

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

0投票
by

评论由:mfikes制作

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

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