分享你的想法,参与<俺主页链接>2024 年 Clojure 状态调查!2024 State of Clojure Survey!

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

+3

我认为使用代码(`#`语法)的自动 gensym 在递归使用时会覆盖自身,这似乎像一个错误。jumar 确认这发生在 1.10.3、1.6.0、1.3.0 和 1.0.0。

(defmacro recursive-macro-1 [[x & more :as xs] acc]
  (if (seq xs)
    `(let [x# ~x]
       (recursive-macro-1 ~more (conj ~acc x#)))
     acc))

(defmacro recursive-macro-2 [[x & more :as xs] acc]
  (if (seq xs)
    (let [gx (gensym 'x)]
      `(let [~gx ~x]
         (recursive-macro-2 ~more (conj ~acc ~gx))))
     acc))

(comment
  (recursive-macro-1 [1 2] []) ;; => [2 2]
  (recursive-macro-2 [1 2] []) ;; => [1 2]
  )

我预期《recursive-macro-1》应与《recursive-macro-2》产生相同的结果,但您可以通过运行 clojure.walk/macroexpand-all 在这些形式上看到,覆盖阻止了这一点。

;; auto-gensym - the name of the local binding is the same in the nested let*
(let* [x__10885__auto__ 1]
  (let* [x__10885__auto__ 2]
    (conj (conj [] x__10885__auto__) x__10885__auto__)))

;; gensym
(let* [x11095 1]
  (let* [x11096 2] 
    (conj (conj [] x11095) x11096)))

1 答案

+2

select by
 
最佳答案

自动 gensym 在读取时进行 gensym,而不是在宏展开时。

为了使自动 gensym 在宏展开时进行 gensym,可以更改语法引号读取器以生成 let 绑定。

目前:
用户=> (read-string "`(x#)") (clojure.core/seq (clojure.core/concat (clojure.core/list (quote x__9__auto__))))

变为:
用户=> (read-string "`(x#)}) (clojure.core/let [foo (gensym 'x)] (clojure.core/seq (clojure.core/concat (clojure.core/list foo)))

此更改的后果是,clojure.core/and 这样的宏(随机选择了一个常用宏,它在 gensym 中使用 gensyms)每次展开时都会生成新的符号,而不是在实现读取时一次性生成。这可能没事?gensym 应该很快,新符号在编译时使用后会被忘记,gensym 命名计数器可以数得非常高。

谢谢 - 我已经注意到,当运行我的表单通过 `clojure.tools.reader/read-string` 时,可能存在这种情况。我相信我漏掉了什么,但是宏展开不是每次都会再次运行形式通过读取器(这样可以增加 gensym id 计数器)吗?无论如何,您上面的建议看起来非常合理。这是否可以作为一个 Clojure JIRA 问题由我(或某个人)提出,或者这需要核心团队在此前的某种形式认可?
创建一个很好地阐述的票证是有价值的。不清楚它会在优先级中处于什么位置,但有明确的建立问题陈述和替代方案想法将有助于我们更快地确定。
@Fogus 我发现了 - 从我在clojurians #clojure-dev最近的问题中,我无法创建一个JIRA票证,因为我不是贡献者,但我确实认为这很有价值。
我在 `clojure.core/and` 实例上遇到了这个问题。它总是使用 gensym `and__5579__auto__`。这在将 Clojure 代码转换为 Java 时创建了问题,因为 Java 不允许有覆盖的局部变量。我同意 Tom Dalziel 的观点,递增 gensym id 计数器会有效。

@Fogus 希望的行为是我们不会全局重复使用相同的 gensym 符号。
这似乎是你的编译器的问题?

如果你正在处理Clojure源代码并编译为Java,那么Clojure允许在嵌套静态作用域中映射相同名称,你需要通过类似词法转换的方法来处理这些问题。

如果你正在将JVM字节码反汇编为Java,JVM实际上根本不命名局部变量,它们只是按照编号进行排序。名称存储为可选调试信息,而且没有唯一性的要求 [链接](https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.13)

因此,在这两种情况下,为了忠实地捕获语义(Clojure->Java,JVM字节码->Java),你需要能够处理可能重复的名称
...