在你的想法中,请在 Clojure 2024 年全球调查!中分享。

欢迎!请查看关于页面,了解更多关于这个网站如何工作的信息。

+17
ClojureScript

如果你在两个命名空间中使用了相同名称的 fn 并使用 letfn,则可能会使用错误的 fn。

`
(ns hello-world.one)

(letfn [(answer [] "1")]
(defn get-answer []

(answer)))

`

`
(ns hello-world.two)

(letfn [(answer [] "2")]
(defn get-answer []

(answer)))

`

`
(ns hello-world.core)
(:require [hello-world.one])

        [hello-world.two]))

(println "one =>" (hello-world.one/get-answer)) ; one => 1
(println "two =>" (hello-world.two/get-answer)) ; two => 1 WHAT?!?
`

这个问题似乎存在于顶层 letfn 以及非顶层 (let (link: ) (letfn (link: ...))) 中。

10 个回答

0

评论者:jeremyrsellars

这个补丁在函数声明中包装了 letfn :expr 和 :statement 形式(以前,只有 :expr 形式被包装)。

我仅仅进行了最小限度的测试。它修复了描述中的代码。

注意:这是我第一次尝试编译器,因此完全有可能我没有理解整个问题。

0

评论者:mfikes

确认了这个补丁在自托管 ClojureScript (Planck) 中的问题处理是对的。

没有补丁

cljs.user=> (require 'hello-world.core) one => 2 two => 2 nil

补丁后

cljs.user=> (require 'hello-world.core) one => 1 two => 2 nil

0

评论由:dpsutton发表

这不需要不同的命名空间。问题是 let-fn 正在将其绑定作为全局变量。

简单的复现方法是:
1. lein new mies letfn-bug,
2. 更新 ClojureScript 版本为 (link: org.clojure/clojurescript "1.9.946")
3. 然后

`
(ns letfn-bug.core
(:require [clojure.browser.repl :as repl]))

(enable-console-print!)

(letfn [(non-unique-name [] 4)]
(defn f1 [] (non-unique-name)))

(letfn [(non-unique-name [] 5)]
(defn f2 [] (non-unique-name)))

(println "应该是4-> " (f1))
(println "应该是5-> " (f2))
`

然后是 scripts/repl

结果是

`
cljs.user=> (load-file "letfn_bug/core.cljs")
应该是4-> 5
应该是5-> 5
nil
cljs.user=>

`

生成的 JavaScript 代码如下:

// 由 ClojureScript 1.9.946 编译 {} goog.provide('letfn_bug.core'); goog.require('cljs.core'); goog.require('clojure.browser.repl'); cljs.core.enable_console_print_BANG_.call(null); var non_unique_name = (function letfn_bug$core$non_unique_name(){ return (4); }); letfn_bug.core.f1 = (function letfn_bug$core$f1(){ return non_unique_name.call(null); }); var non_unique_name = (function letfn_bug$core$non_unique_name(){ return (5); }); letfn_bug.core.f2 = (function letfn_bug$core$f2(){ return non_unique_name.call(null); }); cljs.core.println.call(null,"应该是4-> ",letfn_bug.core.f1.call(null)); cljs.core.println.call(null,"应该是5-> ",letfn_bug.core.f2.call(null));

0
by

评论由:dnolen发表

快速审查补丁,我们不应该总是将语句包装在语句上下文中,可能更好的是只在顶层这样做,这样这里才真正有问题。

0
by

评论由:gnl发表

我只是想表示,我们应该提高这个问题的优先级。这样一个编译器中断作用域的问题绝不是小问题。

0
by

评论者:jeremyrsellars

此补丁类似于第一个,但是只将函数包裹语句应用于函数作用域之外的定义 letfn 形式。(CLJS-1965-Wrap-top-level-letfn-to-avoid-collisions.patch)

0
by

评论由:timothypratley发表

我已在本地确认新补丁效果良好...但我不得不手动应用更改,因为 git apply 失败。如果Jeremy创建了一个新的补丁,这个看起来可以吗?请告诉我是否有我可以帮忙的地方(如果这能帮助他保留信用,我很乐意更新他的补丁?)

0
by

评论由:dnolen发表

在顶级闭包周围包装可能会引入与其他高级Closing的不希望有的交互 - 在继续进行提出的补丁之前,我想看看DCE等是否仍适用于此模式。

另一种选择是跟踪此模式并将其分配给命名空间加阴影逻辑(即一个命名空间中多次使用相同名称)。

0
by
_评论由: jeremyrsellars_ 发布

以下是截至r1.10.520的最新Wrap-top-level-letfn-to-avoid-collisions补丁更新。

我无法想到一种方法来使用deftest测试死代码消除,但我们预计此高级编译输出示例将包含 "kwc",但不包含 "dce"。

{code:title=letfn_dce_test.cljs|borderStyle=solid}
(ns cljs.letfn-dce-test
  (:require [cljs.test :refer-macros [deftest is]]))

(letfn [(fn-to-kwc [] :kwc)
        (fn-to-dce [] :dce)]  ; 这个函数和关键字应该被DCE消除
  (defn f0 [] (count (str f (name (fn-to-kwc)))))
  (deftest letfn-live-code-test
    (is (pos? (f0)))))


我观察到它表现得好像死代码消除仍然在起作用。


bin/cljsc src/test/cljs/cljs/letfn_dce_test.cljs "{:optimizations :advanced}"|less


David Nolan的替代方案很有趣。还有其他原因要追求将letfns附加到命名空间吗?
0
by
参考:[https://clojure.atlassian.net/browse/CLJS-1965](https://clojure.atlassian.net/browse/CLJS-1965)(由jeremyrsellars报告)
...