如何将 gensym 应用于每个特定变量

how to apply gensym to each specific variable

我想写一个宏(my-dotimes [x init end] & body)来计算从 init 到 end-1 的 x 的 body 值,增量为 1。在这里你必须再次确保避免"variable capture problem"。它应该像这样工作:

user=> (my-dotimes [x 0 4] (print x))
0123nil

我的代码是:

(defmacro my-dotimes [[x initial end] & body]
`(loop [i# ~initial]
    (when (< i# ~end)
        ~@body
        (recur (inc i#))))))

但是当我使用 macroexpand 检查它并发现:

user=> (macroexpand '(my-dotimes [x 0 4] (println x)))
(loop* [i__4548__auto__ 0] (clojure.core/when (clojure.core/<i__4548__auto__ 4)
 (println x) 
(recur (clojure.core/inc i__4548__auto__))))

我在想怎么改

(println x) => (clojure.core/println i__4548__auto__)

此处,您提供应绑定到计数器的符号(此处 x),因此您不需要 使用 gensyms。 不要使用 i#,只需引入宏的用户给你的符号即可。 当您引入新符号并且不希望它们与现有符号冲突时,您需要 gensyms。

在 Common Lisp 中,用 user-supplied 符号到 i 的当前值的绑定来包装 body 是有意义的,使用 (let ((,x ,i)) ,@body),因为用户的代码可能会在迭代期间更改计数器的值(这可能很糟糕)。但是这里我认为你不能直接改变变量,所以你不需要担心。

你的第二个例子是:

(defmacro for-loop [[symb ini t change] & body]
  `(loop [symb# ~ini] 
     (if ~t 
         ~@body
         (recur ~change))))

第一个问题:当您扩展 body 时,它可能是一个或多个表单,您将 end-up 得到一个包含许多分支而不是 2 个分支的 if 表单。例如 (if test x1 x2 x3 (recur ...)) 如果您的 body 包含 x1x2x3。您需要用 do 表达式包裹主体,使用 (do ~@body).

现在,情况与以前没有太大不同:您仍然有一个符号,由用户提供,您负责在宏中建立绑定。不要使用 symb#,它会创建一个新的符号,完全不同于 symb,只需直接使用 symb。 例如,您可以这样做(未经测试):

(defmacro for-loop [[symb init test change] &body]
  `(loop [~symb ~init]
     (if ~test (do ~@body) (recur ~change))))

只要您使用宏调用者提供的符号,就不需要 gensym。当您必须在生成的代码中创建一个新变量时,您需要 gensyms,这需要一个新的符号。例如,您只对一个表达式求值一次,并且需要一个变量来保存它的值:

(defmacro dup [expr]
  `(let [var# ~expr]
      [var# var#]))