(setf list (loop for i from 1 to 12 collect i))
(defun removef (item seq)
(setf seq (remove item seq)))
CL-USER> (removef 2 list)
(1 3 4 5 6 7 8 9 10 11 12)
CL-USER> (removef 3 list)
(1 2 4 5 6 7 8 9 10 11 12)
为什么不removef
真正修改变量?
(setf list (loop for i from 1 to 12 collect i))
(defun removef (item seq)
(setf seq (remove item seq)))
CL-USER> (removef 2 list)
(1 3 4 5 6 7 8 9 10 11 12)
CL-USER> (removef 3 list)
(1 2 4 5 6 7 8 9 10 11 12)
为什么不removef
真正修改变量?
在 Common Lisp 中,参数是“通过身份”传递的(这个术语可以追溯到 D. Rettig,他是 Allegro Common Lisp 实现的开发者之一)。想想指针(指向堆对象)是按值传递的,这对于大多数 Lisp 对象(如字符串、向量,当然还有列表)都是如此;事情稍微复杂一些,因为实现也可能有立即值,但除此之外这里的重点)。
setf
of修改函数的seq
(私有,词法)变量绑定。此更改在removef
.
为了removef
能够在调用点影响周围环境,您需要将其设为宏:
(defmacro removef (element place)
`(setf ,place (remove ,element ,place)))
您可能想了解一下泛化引用setf
的概念。请注意,我上面提供的宏版本并不是实际应该如何完成的!有关详细信息,请阅读有关及其丑陋的细节。removef
get-setf-expansion
如果您只想破坏性地修改列表,请考虑使用delete
而不是删除,但请注意,这可能会产生意想不到的后果:
(delete 2 '(1 2 3 4))
ANSI 标准不允许(您正在破坏性地修改文字对象,即代码的一部分)。在这个例子中,这个错误很容易被发现,但如果你在某个调用堆栈中的深度为 7 帧,处理的值的来源对你来说并不完全清楚,这就会成为一个真正的问题。无论如何,即使
(setf list (list 1 2 3 4))
(delete 1 list)
list
起初可能会令人惊讶,即使
(setf list (list 1 2 3 4))
(delete 2 list)
list
似乎“工作”。本质上,第一个示例没有按预期工作,因为该函数delete
与您的原始版本有相同的问题removef
,即它不能改变调用者对list
变量的概念,所以即使是破坏性版本,正确的做法是:
(setf list (delete 1 (list 1 2 3 4)))
removef
如@Dirk所述,这是“能够在调用点影响周围环境”的实现示例。
(defmacro removef (item place &rest args &key from-end test test-not start end count key &environment env)
(declare (ignore from-end test test-not start end count key))
(multiple-value-bind (vars vals store-vars writer-form reader-form)
(get-setf-expansion place env)
(assert (length= store-vars 1) ()
"removef only supports single-value places")
(let ((v.args (make-gensym-list (length args)))
(store-var (first store-vars)))
(once-only (item)
`(let* (,@(mapcar #'(lambda (var val)
`(,var ,val))
vars vals)
,@(mapcar #'(lambda (v.arg arg)
`(,v.arg ,arg))
v.args args)
(,store-var (remove ,item ,reader-form ,@v.args)))
,writer-form)))))
实用程序length=、make-gensym-list和once-only在 Project Alexandria中可用。
顺便说一句,Alexandria 存在一个使用define-modify-macro但需要辅助定义的removef定义。此版本不需要辅助定义。