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

谢谢 - 我在我通过`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 bytecode->java),你需要能够处理可能的重命名
...