(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
没有真正修改变量?
答案 0 :(得分:7)
在Common Lisp中,参数是“通过身份”传递的(this term goes back to D. Rettig,是Allegro Common Lisp实现的开发人员之一)。想想通过值传递的指针(对堆对象),对于大多数Lisp对象(如字符串,向量,当然还有列表)都是如此;事情稍微复杂一些,因为实现也可能具有直接值,但是旁边这一点)。
setf
的{{1}}修改了函数的(私有,词法)变量绑定。此更改在seq
之外不可见。
为了使removef
能够在通话时影响周围环境,您需要将其设为宏:
removef
您可能需要查看setf
和generalized references的概念。请注意,我在上面提供的(defmacro removef (element place)
`(setf ,place (remove ,element ,place)))
的宏版本是不它应该如何实际完成!有关详细信息,请参阅get-setf-expansion
及其丑陋的详细信息。
如果您想要破坏性地修改列表,请考虑使用removef
而不是删除,但请注意,这可能会产生意想不到的后果:
delete
ANSI标准不允许(您破坏性地修改文字对象,即代码的一部分)。在这个例子中,错误很容易被发现,但是如果你在某个callstack中有7帧深,那么处理起源并不完全清楚的值,这就成了一个真正的问题。无论如何,甚至
(delete 2 '(1 2 3 4))
一开始,可能会令人惊讶,即使
(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
答案 1 :(得分:1)
以下是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和Alexandria。
BTW存在于Alexandria removef定义,使用define-modify-macro但需要辅助定义。此版本不需要辅助定义。