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

欢迎!有关此工作方式的更多信息,请参阅关于页面。

0 votes
ClojureScript
CLJS 编译器将为内部在 {{let}} (以及在某些情况下外部)创建的函数生成大量不必要的函数包装器。当在 {{loop}} 内部或在函数使用 {{recur}} 来解决 JS {{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';
        >// this could just be 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);
    返回(function(x, y, z, a, b, c, map__527, map__527__$1, p, foo, bar, coll) {
        返回一个函数 test$app$dummy_$_return() {
            返回[a, b, c];
        };
    })(x, y, z, a, b, c, map__527, map__527__$1, p, foo, bar, coll);
};

印象流源路径标识符=app.js.map


我还没有进行基准测试,但我非常确信这也有一定的运行时开销。

导致此问题的代码应仅针对实际需要发出这些包装器,并且仅针对实际使用的局部变量进行优化。

或者,我们可以使用JS {{const}}(或{{let}}),因为现在>98%的浏览器都支持这个[2]而且Closure编译器会根据一定的{{:language-out}}设置为我们生成这些函数包装器(例如,当前默认值)。
 

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

14 个答案

0 votes
by

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

评论由:dnolen 制作

我喜欢使用JavaScript let进行绑定的想法,让Closure来解决这个问题。这将减少ClojureScript中我们手动处理这些的很多自定义代码。

0 votes
评论由: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 发生了什么事情。通过查看代码,我认为它应该总是导致节点进程崩溃。我搞不清楚为什么它能在 REPL 中工作,但我在使用常规编译和手动运行节点时可以可靠地重现崩溃,无论是带补丁还是不带补丁。

在 core.async 主仓库和 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 -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)


使用我应用了补丁的分叉仓库,我得到一个不同的堆栈跟踪(预期)和相同的失败

$ 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__20298__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 votes
评论由: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
    在 /Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3318:19
    在 /Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3355:51
    在 cljs$core$async$tests$state_machine__18881__auto____1 (/Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3376:4)
    在 cljs$core$async$tests$state_machine__18881__auto__ (/Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3392:62)
    在 cljs$core$async$impl$ioc_helpers$run_state_machine (/Users/mfikes/Projects/core.async/out/cljs/core/async/impl/ioc_helpers.js:96:74)
    在 cljs$core$async$impl$iocHelpers$run_state_machine_wrapped (/Users/mfikes/Projects/core.async/out/cljs/core/async/impl/ioc_helpers.js:99:63)
    在 /Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3407:67
    在 Immediate.cljs$core$async$impl$dispatch$process_messages (/Users/mfikes/Projects/core.async/out/cljs/core/async/impl/dispatch.js:20:9)
    在 processImmediate (internal/timers.js:443:21)
    在 process.topLevelDomainCallback (domain.js:136:23)
错误:断言失败:预期此异常
false
    在 /Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3923:19
    在 /Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3961:51
    在 cljs$core$async$tests$state_machine__18881__auto____1 (/Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3982:4)
    在 cljs$core$async$tests$state_machine__18881__auto__ (/Users/mfikes/Projects/core.async/out/cljs/core/async/tests.js:3998:62)
    在 cljs$core$async$impl$ioc_helpers$run_state_machine (/Users/mfikes/Projects/core.async/out/cljs/core/async/impl/ioc_helpers.js:96:74)
    在 cljs$core$async$impl$iocHelpers$run_state_machine_wrapped (/Users/mfikes/Projects/core.async/out/cljs/core/async/impl/ioc_helpers.js:99:63)
    在 /Users/mfikes/Projects/core.async/out/cljs/core/async.js:4290:67
    在 /Users/mfikes/Projects/core.async/out/cljs/core/async.js:609:13
    在 /Users/mfikes/Projects/core.async/out/cljs/core/async/impl/channels.js:201:12
    在 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 votes

评论由:thheller 制作

(链接: ~mfikes):是的,这就是我在REPL中运行事物所意味着的。你的方法在补丁下工作,但是没有补丁则不行。但是在编译和运行节点过程分离的情况下,两种情况都会失败。就我所知,你的方法是通过管理的node-repl过程运行代码的。

我不知道为什么你的方法能工作,所以我尝试单独运行事物,以便我可以使用{{node --inspect-brk out/main.js}}来运行进程并附加调试器。然而,由于这种方式总是失败,我认为我的补丁实际上没有在这里出问题。调试像这样的异步代码很难……

0 votes

评论者:mfikes

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

0 votes
توسط

评论由:thheller 制作

فهم من از {{node}} این است که هر فرهنگ‌عمومی (Exception) باقیمانده باید باعث //(لینک: 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 آغاز می‌شود، در حالی که در میدانی‌امNotFoundException من اینطور نیست. نمی‌توانم بفهمم چرا، زیرا کدی که مسئولیت این را دارد، حتی کد 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 votes
توسط

评论者:mfikes

اگر {{core.async}} در {{project.clj}} به‌روزرسانی شود.

  • clojure 1.10.0
  • lein-cljsbuild 1.1.7

سپس تست‌ها را بر اساس توضیحات README {{core.async}} که شامل اجرایش در مرورگر است، اجرا کنید (که شامل اجرای آن‌ها در یک مرورگر می‌شود) آن‌ها با ClojureScript 1.10.520通过的 but never complete完成的with حالت کامل نمی‌شوند (نشان‌دهنده تعداد تست‌های通过的 / شکست‌خورده است) با ClojureScript master با CLJS-3077.patch نوشته شده.

0 votes
توسط

评论由:thheller 制作

Mike، ممنونم. بالاخره فهمیدم ... منطق تصور می‌کرد که تمام فرهنگ‌عمومی‌ها بیرون از حلقه نیاز به بازداشت ندارند. منطق تصور می‌کرد، اما تصور کرد که تطبیق دارد وقتی واقعاً در یک حلقه هستید، که این توسط شناسایی اولین باندهای حلقه انجام می‌شود. این برای حلقه‌های بدون باندها تکمیل نشده بود...

`
(defn foo [a b]
(let [x ...] ;; no capture

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

`

0 votes
توسط

评论由:thheller 制作

پچ جدید باید قابل اعتمادتر باشد (و تست‌های core.async را پاس می‌کند). من همچنین چند از居高 هم اضافه کردم که به طور واضح کثیف هستند اما نمی‌توانم راه پاک‌تری برای تست این پیدا کنم.

0 votes

评论者:mfikes

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

0 votes

评论者:mfikes

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

0 votes

评论者:mfikes

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

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