2024 Clojure状况调查中分享你的想法!

欢迎!请参阅关于页面获取更多关于此的信息。

+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

选中答案
 
最佳答案

自动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(随机选择了一个常用且使用 gensym 的宏)这样的宏在展开时每次都会创建新的符号,而不是在读取实现时只创建一次。这可能不错?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 允许在嵌套静态作用域中使用相同名称的绑定,因此您需要通过某种方式(如 alpha 转换)来处理它。

如果您正在将 JVM 字节码反编译为 Java,实际情况是 JVM 并不真正命名局部变量,只是将它们按顺序编号。这些名称存储为可选的调试信息,并且没有唯一性的要求。[链接](https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7.13)

因此,在这两种情况下,为了忠实地捕获语义(clojure→java,jvm 字节码→java),您需要能够处理可能重复的名称。
...