作为对 Sylwester 回答的说明,这是您的 stack-push 的一个版本,乍一看似乎正确,但实际上有一个丑陋的问题(感谢 jkiiski 指出这一点!),然后是一个更简单的版本,仍然有问题,最后是更简单版本的变体。
这是初始版本。这与您的签名一致(它需要一个不能是列表的单个参数,或者一个参数列表,并根据它看到的内容决定要做什么)。
(defmacro stack-push (stack element/s)
;; buggy, see below!
(let ((en (make-symbol "ELEMENT/S")))
`(let ((,en ,element/s))
(typecase ,en
(list
(setf ,stack (append (reverse ,en)
,stack)))
(t
(setf ,stack (cons ,en ,stack)))))))
但是,我更倾向于使用&rest 参数来编写它,如下所示。这个版本更简单,因为它总是做一件事。不过还是有问题。
(defmacro stack-push* (stack &rest elements)
;; still buggy
`(setf ,stack (append (reverse (list ,@elements)) ,stack)))
这个版本可以作为
(let ((a '()))
(stack-push* a 1 2 3)
(assert (equal a '(3 2 1))))
例如。而且它似乎有效。
多重评价
但它不起作用,因为它可以对不应该被多重评价的事物进行多重评价。最简单的方法(我发现)是查看宏扩展是什么。
我有一个小实用函数来执行此操作,称为macropp:它只是根据您的要求多次调用macroexpand-1,漂亮地打印结果。要查看问题,您需要展开两次:首先展开stack-push*,然后查看生成的seetf 会发生什么情况。第二个扩展是依赖于实现的,但是你可以看到问题。这个示例来自 Clozure CL,它有一个特别简单的扩展:
? (macropp '(stack-push* (foo (a)) 1) 2)
-- (stack-push* (foo (a)) 1)
-> (setf (foo (a)) (append (reverse (list 1)) (foo (a))))
-> (let ((#:g86139 (a)))
(funcall #'(setf foo) (append (reverse (list 1)) (foo (a))) #:g86139))
你可以看到问题:setf 对foo 一无所知,所以它只是调用#'(setf foo)。它小心地确保以正确的顺序评估子表单,但它只是以明显的方式评估第二个子表单,结果(a) 被评估两次,这是错误的:如果它有副作用,那么它们会发生两次。
所以解决这个问题的方法是使用define-modify-macro,他的工作就是解决这个问题。为此,您定义一个创建堆栈的函数,然后使用define-modify-macro 来创建宏:
(defun stackify (s &rest elements)
(append (reverse elements) s))
(define-modify-macro stack-push* (s &rest elements)
stackify)
现在
? (macropp '(stack-push* (foo (a)) 1) 2)
-- (stack-push* (foo (a)) 1)
-> (let* ((#:g86170 (a)) (#:g86169 (stackify (foo #:g86170) 1)))
(funcall #'(setf foo) #:g86169 #:g86170))
-> (let* ((#:g86170 (a)) (#:g86169 (stackify (foo #:g86170) 1)))
(funcall #'(setf foo) #:g86169 #:g86170))
你可以看到现在(a) 只被评估一次(而且你现在只需要一个级别的宏扩展)。
再次感谢 jkiiski 指出错误。
macropp
为了完整起见,这里是我用来漂亮打印宏扩展的函数。这只是一个 hack。
(defun macropp (form &optional (n 1))
(let ((*print-pretty* t))
(loop repeat n
for first = t then nil
for current = (macroexpand-1 form) then (macroexpand-1 current)
when first do (format t "~&-- ~S~%" form)
do (format t "~&-> ~S~%" current)))
(values))