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

选定
 
最佳答案

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

为了使auto 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名称的计数器可以计数得非常高。

谢谢 - 我注意到当我使用 `clojure.tools.reader/read-string` 运行表单时,可能存在这种情况。我相信我可能遗漏了什么,但是否宏展开每次都会重新运行这些表单通过读取器(从而递增 gensym id 计数器)?无论如何,你上面的建议看起来非常有道理。这是否可以(由我或其他人)作为 Clojure JIRA 问题提出,或者这需要核心团队在此之前的某种批准?
我认为一个好的任务描述是有价值的。它不清楚这会在优先事项中的哪个位置,但是一个清晰的aan问题陈述和替代方案思路将帮助我们更快地确定。谢谢!
@Fogus 我发现 - 来自我最近在 clojurians #clojure-dev 中提出的问题,我无法创建 JIRA 票,因为我不是贡献者,但我同意这是值得的。
我遇到了与 `clojure.core/and` 完全相同的问题实例。它总是在使用 gensym `and__5579__auto__`。这当将从 Clojure 代码转换到不允许有相同局部变量名的 Java 代码时产生了问题。我同意 Tom Dalziel 的观点,递增 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),你都需要能够处理可能重复的名称。
...