2024 年 Clojure 状况调查!中分享您的想法。

欢迎!有关本站点如何运作的更多信息,请参阅关于页面。

+17
ClojureScript

如果你在两个命名空间中用一个相同的名称定义了一个 letfn 函数,将会使用错误的函数。

`
(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. 将cljs版本更新到(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=>

`

生成的js代码

// 由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

评论者:dnolen

快速审核补丁,我们可能只需要在实际上是问题的顶层处进行包装,而不是总是包装在语句上下文中。

0

评论者:gnl

我想表达一下我的意见,建议我们提高对此事的优先级。这种导致编译器打破作用域的问题绝对不是一个小问题。

0

评论由:jeremyrsellars 提出

这个补丁与第一个类似,但它仅包裹函数作用域外的语句级别的letfn形式。(CLJS-1965-Wrap-top-level-letfn-to-avoid-collisions.patch)

0

评论者:timothypratley

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

0

评论者:dnolen

在顶级使用闭包可能引入其他与Clojure中的Closure高级的不希望有的交互 - 在继续进行提议的补丁之前,我想看看DCE等是否仍然适用于这种模式。

一个替代方案是跟踪这种模式并将其分配给命名空间+阴影逻辑(即在同一个命名空间中使用同一个名字多次)。

0
_jeremyrsellars发表的评论:

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

我想不出如何用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
参考: https://clojure.atlassian.net/browse/CLJS-1965(由jeremyrsellars报告)
...