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

欢迎!请参阅关于页面,了解更多关于如何使用本网站的信息。

+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 () (letfn (...) ...)

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发表

这不需要不同的命名空间。bug在于 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
by

评论者:dnolen

快速审查补丁,与其总是将它们包裹在语句上下文中,不如在顶级上下文中这样做,因为这是真正的问题所在。

0
by

评论者:gnl

我想提出,这个问题应该提高优先级。这样一个破坏作用域的编译器问题绝对不是一个小问题。

0
by

评论由jeremyrsellars发表

这个补丁与第一个类似,但它只包装了 fn-scope 外部的 letfn 形式的函数。 (CLJS-1965-Wrap-top-level-letfn-to-avoid-collisions.patch)

0
by

评论者:timothypratley

我在本地确认了新补丁的效果...但我必须手动应用这些更改,因为 git apply 失败了。如果 Jeremy 创建一个全新的补丁,这看起来是否可以实施?让我知道我是否可以提供帮助(如果我帮忙更新他的补丁以保留他的信用会怎样?)

0

评论者:dnolen

在顶层封装闭包可能会引入与其他高级闭包的不希望交互 - 在继续提出补丁之前,我想看看DCE等对这个模式是否还能正常工作。

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

0
评论者:jeremyrsellars

以下是关于将顶层letfn包装以避免冲突的补丁的更新,当前版本为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报告)
...