为什么在这个常见的 lisp 宏中使用 `,@` 不能像我预期的那样工作?为什么 Slime 会返回此错误消息?

Why the use of `,@` does not work as I expected in this common lisp macro? Why is Slime returning this error message?

我正在阅读温斯顿写的 Lisp 一书。此外,我正在使用 SBCL、Emacs 和 Slime。

在第8章(关于宏)中,书中有如下练习:

Problem 8-7: A stack is a learnly ordered set of things that can be accessed using push and pop operations. OUR-PUSH adds a new item to the top of the stack, while OUR-POP removes the item on top of the stack. A list can be used to represent a stack, with the first element corresponding to the item on top. Define OUR-PUSH and OUR-POP as macros. OUR-PUSH takes two arguments, the item to be pushed and the name of a variable whose value it the list representing the stack. The value returned is the enlarged list. OUR-POP takes a single element, the name of the variable whose value is in the list. The value returned is the item popped. In both cases the value of the variable is changed to reflect the new state of the stack.

我对 pop 函数做对了。但是,我在 push 重新实现时遇到了麻烦。这就是答案-sheet:

(defmacro book-our-push (item-to-be-pushed stack)
  `(setq ,stack (cons ,item-to-be-pushed ,stack)))

它按预期工作。我最初的回答是相似的:

(defmacro our-push (item-to-be-pushed stack)
  `(setf ,stack (list ,item-to-be-pushed ,stack)))

但这会产生一个意外的列表嵌套,例如:

CL-USER> (defparameter so-stack '(3 4 5 6))
SO-STACK

CL-USER> so-stack
(3 4 5 6)

CL-USER> (our-push 2 so-stack)
(2 (3 4 5 6))

然后,我想:“哦,这一定是你使用,@的那种情况”。因为 ,@ 造成拼接行为。因此,我做了:

(defmacro our-push (item-to-be-pushed stack)
  `(setf ,stack (list ,item-to-be-pushed ,@stack)))

但是,它不起作用。此外,Slime 会抛出一条让我感觉很奇怪的错误消息:

The value
  SO-STACK
is not of type
  LIST

特别是因为:

CL-USER> (listp so-stack)

T

对于这种情况,有没有办法使用 ,@? 为什么 slime 指示变量 so-stack 而不是 列表,而实际上它是一个列表?

你的水平很差。当使用宏函数展开宏时,你是在源码.

上操作

源码是这个list:

(my-push thing foos)

我。 e.包含三个 符号 、名为 my-pushthingfoos.

的列表

宏函数获取此列表并将其转换为不同的列表,在您的情况下:

(setf foos (list thing foos))

然后进一步编译(更多宏扩展,最终编译为机器代码)。

如果你试图拼接 foos 那是不可能的,因为符号不是列表。

宏函数看不到这些符号的含义。它甚至在表单被编译为机器代码之前就完成了。

这是answer-sheet:

(defmacro book-our-push (item-to-be-pushed stack)
  `(setq ,stack (cons ,item-to-be-pushed ,stack)))

正如您所说,您最初的回答是相似的:

(defmacro our-push (item-to-be-pushed stack)
  `(setf ,stack (list ,item-to-be-pushed ,stack)))

而且,本质上,这就是问题所在。 cons 就像一管胶水 - 它将一个值粘贴到现有列表上。 list 只是一个可以装东西的大袋子。

(list 5 '(1 2 3 4)) ; puts 5 and '(1 2 3 4) into a single list, side by side
                    ; => (5 (1 2 3 4))

(cons 5 '(1 2 3 4)) ; attaches 5 to list '(1 2 3 4)
                    ; => (5 1 2 3 4)

通常,您使用列表 - 但在这种情况下不使用。

然后您尝试使用 @ 将这些值拼接在一起。没用。

(defmacro our-push-splice (item-to-be-pushed stack)
  `(setf ,stack (list ,item-to-be-pushed ,@stack)))

找出它为什么不起作用的最好方法是使用 macro-expand-1。这显示了宏实际在做什么,而不是它应该做什么。

CL-USER> (defparameter stack '(1 2 3 4))
STACK
CL-USER> (macroexpand-1 '(our-push 5 stack))
(SETF STACK (LIST 5 STACK))
T
CL-USER> (macroexpand-1 '(our-push-splice 5 stack))
(SETF STACK (LIST 5 . STACK))
T
CL-USER> (our-push 5 stack)
(5 (1 2 3 4))
CL-USER> (our-push-splice 5 stack)
; Evaluation aborted on #<TYPE-ERROR expected-type: LIST datum: STACK>.

所以,拼接尝试失败了,因为似乎期望在计算宏时将堆栈拼接进来。名字被插入,仅此而已。

Svante 的回答是正确的:宏是转换源代码 的函数。但有时通过让宏打印它们正在做的事情来查看这一点很有用。这是一个简单的 hack 来做到这一点(其他人更通用的 hack 是 here)。

(defmacro define-traced-macro (name args &body forms)
  ;; Define a traced macro.  This probably misses edge cases
  `(progn
     (defmacro ,name ,args ,@forms)
     (let ((m (macro-function ',name)))
       (setf (macro-function ',name)
             (lambda (form environment)
               ;; Do it like this so we get the source form even if
               ;; the macro fails
               (let ((*print-pretty* t))
                 (format *trace-output* "~&~S~%" form))
               (let ((*print-pretty* t)
                     (expansion (funcall m form environment)))
                 (format *trace-output* "~& -> ~S~%" expansion)
                 expansion))))
     ',name))

现在,如果您使用 define-traced-macro 而不是 defmacro 定义宏,它将跟踪其扩展:

(define-traced-macro book-our-push (item-to-be-pushed stack)
  `(setq ,stack (cons ,item-to-be-pushed ,stack)))
> (let ((x '(1 2)))
    (book-our-push 3 x)
    x)
(book-our-push 3 x)
 -> (setq x (cons 3 x))
(3 1 2)

这应该可以令人信服地说明为什么您的版本不能工作。


顺便说一句,一旦你有了 define-traced-macro,你就可以用它来重新定义自己(认真地做这件事,你会想通过 hard-wiring 它自己扩展到源中来定义它):

> (define-traced-macro book-our-push (item-to-be-pushed stack)
    `(setq ,stack (cons ,item-to-be-pushed ,stack)))
(define-traced-macro book-our-push (item-to-be-pushed stack)
  `(setq ,stack (cons ,item-to-be-pushed ,stack)))
 -> (progn
      (defmacro book-our-push (item-to-be-pushed stack)
        `(setq ,stack (cons ,item-to-be-pushed ,stack)))
      (let ((m (macro-function 'book-our-push)))
        (setf (macro-function 'book-our-push)
              (lambda (form environment)
                (let ((*print-pretty* t))
                  (format *trace-output* "~&~S~%" form))
                (let ((*print-pretty* t)
                      (expansion (funcall m form environment)))
                  (format *trace-output* "~& -> ~S~%" expansion)
                  expansion))))
      'book-our-push)
book-our-push

这很有趣,如果这是您喜欢的事情。