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

时间:2012-09-29 23:43:19

标签: macros lisp common-lisp gensym

我通过Practical Common Lisp阅读/工作。我在关于在Lisp中构建测试框架的章节。

我有功能"测试 - +"实现如下,它的工作原理:

(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逐步宏扩展它们(使用ctrl-c RET,映射到macroexpand-1)。

所以,"检查"呼叫"测试 - +"扩展到这个:

(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类型的条件]

现在,如果我使用相同的代码,将gensym替换为变量名称,例如" x",它可以正常工作。

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

  1. "测试 - +"宏调用所有这些,工作正常。

  2. "结合 - 结果"的宏观扩展宏运行。

  3. 如果我从" combine-results"的宏扩展中移除gensym,它 工作。

  4. 我唯一可以推测的是你不能使用代码包含gensyms的文字用法。如果是这样,为什么不,以及如何解决这个问题呢?如果那不是解释,那是什么?

    感谢。

2 个答案:

答案 0 :(得分:11)

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

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

答案 1 :(得分:11)

GENSYM创建未加密的符号。当宏运行正常时,这不是问题,因为在整个表达式中替换了相同的未处理符号。

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

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

相关问题