Macros 使用“仅限一次”宏

Macros 使用“仅限一次”宏,macros,common-lisp,practical-common-lisp,Macros,Common Lisp,Practical Common Lisp,在中第8章末尾,Peter Seibel仅显示了一次宏。其目的是缓解用户定义宏中变量求值的一些微妙问题。注意,在这一点上,我并不像在其他一些帖子中那样试图理解这个宏是如何工作的,只是想知道如何正确地使用它: (defmacro once-only ((&rest names) &body body) (let ((gensyms (loop for n in names collect (gensym)))) `(let (,@(loop for g in gensy

在中第8章末尾,Peter Seibel仅显示了一次
宏。其目的是缓解用户定义宏中变量求值的一些微妙问题。注意,在这一点上,我并不像在其他一些帖子中那样试图理解这个宏是如何工作的,只是想知道如何正确地使用它:

(defmacro once-only ((&rest names) &body body)
  (let ((gensyms (loop for n in names collect (gensym))))
    `(let (,@(loop for g in gensyms collect `(,g (gensym))))
      `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
        ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
           ,@body)))))
下面是一个(不正确的)人为宏示例,它试图显示几个变量求值问题。它声称通过某个增量对整数范围进行迭代,返回范围:

(defmacro do-range ((var start stop delta) &body body)
  "Sample macro with faulty variable evaluations."
  `(do ((,var ,start (+ ,var ,delta))
        (limit ,stop))
       ((> ,var limit) (- ,stop ,start))
     ,@body))
例如,
(do range(i 1 15 3)(格式t“~A”i))
应该打印
1 4 7 10 13
,然后返回
14

这些问题包括1)可能捕获第二次出现的
限制
,因为它作为自由变量出现;2)可能捕获绑定变量
限制
,因为它与宏参数中出现的其他变量一起出现在表达式中;3)无序计算,由于
delta
将在
stop
之前求值,即使参数列表中
stop
出现在
delta
之前,并且4)多变量求值,因为
stop
start
会多次求值。据我所知,
只需一次
就可以解决这些问题:

(defmacro do-range ((var start stop delta) &body body)
  (once-only (start stop delta limit)
    `(do ((,var ,start (+ ,var ,delta))
          (limit ,stop))
         ((> ,var limit) (- ,stop ,start))
       ,@body)))
然而,
(macroexpand'(do range(i 1 15 3)(格式t“~A”i))
抱怨
限制
是一个未绑定的变量。如果改用gensyms
切换到
,它应该只处理上述问题1和2,则扩展不会发生意外


这是
仅一次
宏的问题吗?只有一次的
真的解决了上面列出的所有问题吗(也许还有其他问题)?

只有一次的宏

为了消除
N
未使用的警告,我将宏更改为:

(defmacro once-only ((&rest names) &body body)
  (let ((gensyms (loop for nil in names collect (gensym))))
                           ; changed N to NIL, NIL is ignored
    `(let (,@(loop for g in gensyms collect `(,g (gensym))))
      `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
        ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
           ,@body)))))
此宏的目的是确保表达式只按定义的顺序计算一次。为此,它将引入新的不相关变量,并将评估结果与这些变量绑定。在宏中,新变量可用。提供宏本身是为了使编写宏更容易

在DO范围内仅使用一次

仅使用一次的
示例

(defmacro do-range ((var start stop delta) &body body)
  (once-only (start stop delta limit)
    `(do ((,var ,start (+ ,var ,delta))
          (limit ,stop))
         ((> ,var limit) (- ,stop ,start))
         ,@body)))
为什么
列表中只有一次
限制
<代码>限制
未在此处定义<代码>限制
一次性
表单中用作符号,但在表单外部没有绑定

ONCE-ONLY
要求名称列表是符号列表,并且这些名称绑定到表单。在您的情况下,
limit
是一个符号,但未定义

我们需要从姓名列表中删除
limit

(defmacro do-range ((var start stop delta) &body body)
  (once-only (start stop delta)
    `(do ((,var ,start (+ ,var ,delta))
          (limit ,stop))
         ((> ,var limit) (- ,stop ,start))
         ,@body)))
现在,如何处理
限制
?鉴于
仅一次
为名称提供了绑定,包括
停止
,我们可以消除符号
限制
,并将其使用替换为
,停止

(defmacro do-range ((var start stop delta) &body body)
  (once-only (start stop delta)
    `(do ((,var ,start (+ ,var ,delta)))
         ((> ,var ,stop) (- ,stop ,start))
       ,@body)))
例如:

CL-USER 137 > (pprint
               (macroexpand
                '(do-range (i 4 10 2)
                   (print i))))

(LET ((#1=#:G2170 4)
      (#3=#:G2171 10)
      (#2=#:G2172 2))
  (DO ((I #1# (+ I #2#)))
      ((> I #3#) (- #3# #1#))
   (PRINT I)))

为什么您有
限制
?你不能用
替换所有的用法,只在
版本中停止一次
。@melpomene是的,你是对的。但是我想不出更好的简单方法来将两种基本类型的变量捕获问题引入到有缺陷的宏中(上面列为问题1和问题2)。还有其他想法吗?很好,很好!但为了澄清我的理解,不需要从
do
中删除
limit
。只需将其从
变量中删除一次,原始的错误宏就可以工作。@davypough:但随后限制在主体中可见,也可能隐藏另一个限制。这是我们通常想要避免的事情。