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

欢迎!请访问关于页面获取更多关于该工作机制的信息。

+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-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 gensyming在读取时生成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这样的宏(随机选择了一个常用宏,这个宏使用了gensym)每次展开都会生成新的符号,而不是在读取实现时只生成一次。这或许是可以接受的?gensyming应该是快速的,新符号在编译时使用,然后就被忘记了,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符号。
这听起来像是你的transpiler的问题?

如果你正在处理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),你需要能够处理可能重复的名称
...