为什么这个 Lisp 宏作为一个整体工作,即使每个部分都不起作用?



我正在阅读/使用实用公共Lisp。我正在学习关于用Lisp构建测试框架的章节。

我有一个功能"test-+"实现如下,它的工作原理:

(defun test-+ ()
(check
(= (+ 1 2) 3)
(= (+ 5 6) 11)
(= (+ -1 -6) -7)))

记住,我说过,它有效,这就是为什么接下来的事情如此令人困惑。。。。

以下是"test-+"所指的一些代码:

(defmacro check (&body forms)
`(combine-results
,@(loop for f in forms collect `(report-result ,f ',f))))
(defmacro combine-results (&body forms)
(with-gensyms (result)
`(let ((,result t))
,@(loop for f in forms collect `(unless ,f (setf ,result nil)))
,result)))
(defmacro with-gensyms ((&rest names) &body body)
`(let ,(loop for n in names collect `(,n (gensym)))
,@body))
(defun report-result (value form)
(format t "~:[FAIL~;pass~] ... ~a~%" value form)
value)

现在,我一直在做的是使用Slime一步一步地对这些进行宏扩展(使用映射到macroexpand-1的ctrl-c RET)。

因此,"test-+"的"check"调用扩展为:

(COMBINE-RESULTS
(REPORT-RESULT (= (+ 1 2) 3) '(= (+ 1 2) 3))
(REPORT-RESULT (= (+ 5 6) 11) '(= (+ 5 6) 11))
(REPORT-RESULT (= (+ -1 -6) -7) '(= (+ -1 -6) -7)))

然后那个宏扩展到这个:

(LET ((#:G2867 T))
(UNLESS (REPORT-RESULT (= (+ 1 2) 3) '(= (+ 1 2) 3)) (SETF #:G2867 NIL))
(UNLESS (REPORT-RESULT (= (+ 5 6) 11) '(= (+ 5 6) 11)) (SETF #:G2867 NIL))
(UNLESS (REPORT-RESULT (= (+ -1 -6) -7) '(= (+ -1 -6) -7))
(SETF #:G2867 NIL))
#:G2867)

正是这个代码,就在这个句子的正上方,不起作用。如果我把它粘贴到REPL中,我会得到以下错误(我使用的是Clozure Common Lisp):

未绑定变量:#:G2867〔类型为Unbound-variable的条件〕

现在,如果我使用相同的代码,用变量名(如"x")替换gensym,它就可以正常工作了。

那么,我们如何解释以下惊喜:

  1. 调用所有这些的"test-+"宏运行良好。

  2. "合并结果"宏的宏扩展不会运行

  3. 如果我从"组合结果"的宏观扩展中删除gensym,它工作。

我唯一能推测的是,你不能使用包含genstyms字面用法的代码。如果是,为什么不呢?如何解决这个问题?如果这不是解释,那是什么?

谢谢。

GENSYM创建未连接的符号。当宏正常运行时,这不是问题,因为在整个表达式中都会替换相同的未理解符号。

但是,当您将表达式复制并粘贴到REPL中时,不会发生这种情况。#:告诉阅读器返回一个未理解的符号。因此,每次出现#:G2867都是一个不同的符号,并且会得到未绑定变量警告。

如果在执行MACROEXPAND之前执行(setq *print-circle* t),则会使用#n=#n#表示法将相同的符号链接在一起。

打印并读回后的代码不再是同一个代码。特别是,打印表示中#:G2867的两个实例将作为两个分离的符号(尽管共享相同的名称)读回,而它们在原始内部表示中应该相同。

尝试将*PRINT-CIRCLE*设置为T,以在宏扩展代码的打印表示中保留标识。

最新更新