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

欢迎!请查看关于页面获取更多关于这个站点工作的信息。

+3

我认为当使用递归来使用 auto-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

选择
 
最佳答案

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

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

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

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

该更改的一个后果可能是像clojure.core/and(随机选择一个使用gensyms的常用宏)这样的宏在展开时每次都会生成新的符号,而不是在读取实现时只生成一次。这可能会很合适吗?gensym应该很快,这些新符号在编译时使用后就会被遗忘,gensym名字的计数器可以计数到很高的值。

by
谢谢 - 我在运行我的表单通过`clojure.tools.reader/read-string`时注意到了这一点。我相信我忽略了一些东西,但是宏展开每次是否都会重新运行表单以通过读取器(从而递增gensym id计数器)?无论如何,您上面的建议看起来非常合理。这是否可以作为一个Clojure JIRA问题提出,或者在此之前需要核心团队的某种形式的赞成意见?
by
我认为一个描述得当的工单是值得的。不清楚它在优先级中的位置在哪里,但明确的问题陈述和替代方法的想法将帮助我们更快地确定这一点。谢谢!
by
@Fogus 我发现 - 从我在clojurians #clojure-dev最近的问题中,我不会创建JIRA工单,因为我不是贡献者,但我同意它是有价值的。
by
我遇到了`clojure.core/and`的这个问题,它总是会使用 gensym `and__5579__auto__`。这当将 Clojure 代码转译成 Java 时,就会出问题,因为 Java 不允许使用重影的局部变量。我同意汤姆·达齐尔的观点,增加 gensym id 计数器是可行的。

@Fogus,期望的行为是我们不全局重用同一个 gensym 符号。
by
这听起来更像是你转译器的问题?

如果你正在操作 Clojure 源代码并编译成 Java,那么 Clojure 吧允许在嵌套静态作用域中使用相同名称的绑定,所以你可能需要通过类似 alpha 转换的方式来处理这个问题。

如果你正在将 JVM 字节码反编译成 Java,JVM 实际上并不为局部变量命名,它们只是得到了编号。名字被存储为可选的调试信息,并且没有要求唯一性https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.13

所以在两种情况下,要忠实于语义(Clojure->Java,JVM 字节码->Java),你需要能够处理可能重复的名称
...