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

欢迎!请访问关于页面,以获取更多关于此功能的信息。

+17
ClojureScript

如果你在两个命名空间中用相同的名称 letfn 定义一个 fn,将使用错误的 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. 将 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

此补丁与第一个补丁类似,但它只将 fn-scope 之外语法的 letfn 表单包裹在函数中。(CLJS-1965-Wrap-top-level-letfn-to-avoid-collisions.patch)

0
答者:

评论者:timothypratley

我在本地确认了新补丁可以正常工作...但由于 git apply 失败,我不得不手动应用更改。如果杰里米创建一个新的补丁,这是否可以顺利进行?如果我可以以任何方式帮忙(如果这样做能帮助他保留荣誉,我愿意更新他的补丁?)请告诉我。

0

评论者:dnolen

在顶级封装闭包可能会引起与其他 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


大卫·诺兰的替代方案很吸引人。是否有更多原因追求将 letfns 语句附加到命名空间上?
0
参考:https://clojure.atlassian.net/browse/CLJS-1965(由 jeremyrsellars 报告)
...