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

欢迎!请访问关于页面以了解更多此功能的信息。

+3

我认为使用(使用#语法)的auto-gensym在递归使用时会自行影射,这似乎像是一个bug。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-1recursive-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名称的计数器可以非常高。

谢谢 - 我已经注意到当我的表单通过 `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),你需要能够处理可能重复的名称
...