使用宏弹出列表中的第一个元素

Pop first element in a list using a macro

我有以下代码

(defmacro popm(l)
`(prog1 (car ,l)
    (setf ,l (cdr ,l))))

应该从列表中弹出第一个元素。但是,我不知道如何调用宏。我尝试使用 (popm (2 3 4)) 但出现错误“EVAL:2 不是函数名称;尝试使用符号代替”。谁能指出我正确的方向?

调试宏的正确方法是使用macroexpand-1:

(macroexpand-1 '(popm (1 2 3)))
(PROG1 (CAR (1 2 3)) (SETF (1 2 3) (CDR (1 2 3))))

现在应该清楚为什么你的宏不起作用了:它不能与常量对象一起使用。

虽然它确实适用于变量:

(defparameter a '(1 2 3))
(popm a)
==> 1
a
==> (2 3)

首先,不要修改文字。

其次,作为从变量(或更一般地,一个地方)中保存的列表中弹出值的宏,您的宏是不安全的,因为它对其参数求值不止一次。相反,它应该是这样的:

(defmacro popm (place)
  (let ((<v> (make-symbol "V")))
    `(let ((,<v> ,place))               ;evaluate PLACE only once
       (prog1 (car ,<v>)
         (setf ,place (cdr ,<v>))))))

最后,如果你想做的是改变列表结构(但不是文字!),你可以做到,而且你不需要宏。这是一个几乎可以做到这一点的函数:

(defun pop-list (l)
  ;; BROKEN
  (prog1 (car l)
    (setf (car l) (cadr l)
          (cdr l) (cddr l))))

这似乎有效:

> (let ((l (list 1 2 3))) ;note not a literal!
    (values (pop-list l)
            l))
1
(2 3)

看起来不错。除非它实际上不起作用:

> (let ((l (list 1 2 3)))
    (values (pop-list l)
            (pop-list l)
            (pop-list l)
            l))
1
2
3
(nil)

它无法工作,因为在列表是单个缺点的地方它无法处理它(这就是为什么 popm 需要是一个宏)。有两种解决方案:一种是说,好吧,弹出所有内容后,它始终只是 returns nil。但是你不知道列表是否为空。那么,好吧,这是另一种方法:

(defun pop-list (l)
  ;; Returns two values: the thing popped, and whether this was the
  ;; last thing.  Popping a broken heart is an error.
  (let ((broken-heart (load-time-value (make-symbol "BROKEN-HEART") t)))
    (cond
     ((eq (car l) broken-heart)
      (error "broken heart"))
     ((null (cdr l))
      (values (prog1 (car l)
                (setf (car l) broken-heart))
              t))
     (t
      (values (prog1 (car l)
                (setf (car l) (cadr l)
                      (cdr l) (cddr l)))
              nil)))))

现在

> (let ((l (list 1 2 3)))
    (pop-list l))
1
nil

> (let ((l (list 1 2 3)))
    (pop-list l)
    (pop-list l)
    (pop-list l))
3
t

> (let ((l (list 1 2 3)))
    (pop-list l)
    (pop-list l)
    (pop-list l)
    (pop-list l))

Error: broken heart