2

我想编写一个宏(my-dotimes [x init end] & body),计算从 init 到 end-1 以 1 为增量的 x 的 body 值。在这里,您必须再次确保避免使用“变量”捕获问题”。它应该像这样工作:

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__)
4

1 回答 1

4

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

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

你的第二个例子是:

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

第一个问题:当您扩展可能是一个或多个表单的主体时,您最终会得到一个if包含许多分支而不是 2 的表单。例如(if test x1 x2 x3 (recur ...)),如果您的主体包含x1,x2x3. 您需要将正文包装在do表达式中,使用(do ~@body).

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

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

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

(defmacro dup [expr]
  `(let [var# ~expr]
      [var# var#]))
于 2016-04-04T16:11:27.737 回答