使用宏弹出列表中的第一个元素
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
我有以下代码
(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