gpt4 book ai didi

macros - 使用 `once-only`宏

转载 作者:行者123 更新时间:2023-12-02 09:20:18 25 4
gpt4 key购买 nike

Practical Common Lisp 第 8 章末尾,Peter Seibel 介绍了once-only 宏。其目的是缓解用户定义宏中变量求值的一些微妙问题。请注意,我现在并不是想了解这个宏是如何工作的,就像其他一些帖子中那样,而是只是想了解如何正确使用它:

(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) (format t "~A "i)) 应打印 1 4 7 10 13 然后返回14

问题包括 1) 可能捕获第二次出现的 limit,因为它作为自由变量出现,2) 可能捕获第一次出现的绑定(bind)变量 limit code>,因为它与出现在宏参数中的其他变量一起出现在表达式中,3)乱序求值,因为 delta 将在 stop 之前求值,即使stop 出现在参数列表中的 delta 之前,并且 4) 多个变量求值,因为 stopstart 被求值不止一次。据我了解,once-only 应该可以解决这些问题:

(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) (format t "~A "i))) 提示 limit 是一个未绑定(bind)的变量。如果我切换到 with-gensyms(它应该只解决上面的问题 1 和 2),扩展就会顺利进行。

这是once-only宏的问题吗? once-only 真的能解决上述所有问题(也许还有其他问题)吗?

最佳答案

一次性宏

为了消除 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)))))

此宏的目的是确保表达式仅按定义的顺序计算一次。为此,它将引入新的未驻留变量,并将评估结果与这些变量绑定(bind)。在宏内部,新变量可用。提供宏本身是为了使编写宏更容易。

在 DO-RANGE 中仅使用一次

您使用ONCE-ONLY的示例:

(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列表中有LIMITlimit 在那里未定义。 LIMITONCE-ONLY 形式内部用作符号,但在外部没有绑定(bind)。

ONCE-ONLY 期望名称列表是符号列表,并且这些名称绑定(bind)到表单。在您的情况下 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)))

现在,如何处理LIMIT?鉴于 once-only 提供了名称绑定(bind),包括 STOP,我们可以消除符号 LIMIT 并将其使用替换为 ,停止:

(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)))

关于macros - 使用 `once-only`宏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43191760/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com