15

在 Clojure 中,您需要gensym在宏中创建供内部使用的符号,以保持它们的卫生。但是,有时您需要在嵌套语法引号中使用相同的符号。例如,如果我想将一个值绑定到一个符号,let并在展开的循环中打印三次,我会这样做

`(let [x# 1]
   ~@(repeat 3
             `(println x#)))

但这会产生

(clojure.core/let [x__2__auto__ 1]
                  (clojure.core/println x__1__auto__)
                  (clojure.core/println x__1__auto__)
                  (clojure.core/println x__1__auto__))

x#let在表单中生成与嵌套在其中的表单不同的符号println- 因为它们是从不同的语法引号创建的。

为了解决它,我可以预先生成符号并将其注入到语法引号中:

(let [x (gensym)]
  `(let [~x 1]
     ~@(repeat 3
               `(println ~x)))
) 

这将产生正确的结果,在任何地方都需要相同的符号:

(clojure.core/let [G__7 1]
                  (clojure.core/println G__7)
                  (clojure.core/println G__7)
                  (clojure.core/println G__7))

现在,虽然它确实产生了正确的结果,但代码本身看起来既丑陋又冗长。我不喜欢必须“声明”一个符号,并且注入语法使它看起来像是来自宏外部,或者在其中某处计算。我希望能够使用 auto-gensym 语法,这清楚地表明这些是宏内部符号。

那么,有没有办法使用带有嵌套语法引号的 auto-gensym 并使它们产生相同的符号?

4

3 回答 3

12

自动生成符号仅在定义它们的语法引用中有效,并且它们在未引用的代码中不起作用,因为这不是语法引用的一部分。

这里符号x#被它的 gensym 替换,因为它在语法引用的范围内:

core> `(let [x# 1] x#)
(clojure.core/let [x__1942__auto__ 1] x__1942__auto__)

如果你取消引用它,它就不再被翻译成它的语法引用:

core> `(let [x# 1] ~@x#)
CompilerException java.lang.RuntimeException: Unable to resolve symbol: x# in this context, compiling:(NO_SOURCE_PATH:1) 

Auto-gensyms 是语法引用中的一个非常方便的快捷方式,您显然需要gensym直接使用其他任何地方,就像您后面的示例一样。

还有其他方法可以构造这个宏,因此 autogensyms 可以工作,尽管在宏顶部的 let 中声明 gensymed 符号在 Clojure 和其他 lisp 中也是非常正常的。

于 2012-10-01T16:04:00.833 回答
9

您的方法(调用 gensym)是正确的方法。

但是,在某些情况下,您可以巧妙地使用doto,->->>。看:

 `(let [x# 1]
   (doto x#
     ~@(repeat 3 `println)))
于 2012-10-02T09:36:34.060 回答
0

更一般地说,每当遇到这种情况时,您都可以执行以下操作:

(let [x `x#]
   `(let [~x 1]
      ~@(repeat 3
                `(println ~x))))

为了清楚起见,您创建自动生成符号并将其绑定到语法引用形式之外,然后在任何需要它的嵌套形式中,您可以使用语法取消引用。

于 2021-06-20T05:53:05.010 回答