3
(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真正修改变量?

4

2 回答 2

7

在 Common Lisp 中,参数是“通过身份”传递的(这个术语可以追溯到 D. Rettig,他是 Allegro Common Lisp 实现的开发者之一)。想想指针(指向堆对象)是按值传递的,这对于大多数 Lisp 对象(如字符串、向量,当然还有列表)都是如此;事情稍微复杂一些,因为实现也可能有立即值,但除此之外这里的重点)。

setfof修改函数的seq(私有,词法)变量绑定。此更改在removef.

为了removef能够在调用点影响周围环境,您需要将其设为宏:

(defmacro removef (element place)
   `(setf ,place (remove ,element ,place)))

您可能想了解一下泛化引用setf的概念。请注意,我上面提供的宏版本并不是实际应该如何完成的!有关详细信息,请阅读有关及其丑陋的细节。removefget-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)))
于 2013-06-13T15:07:20.277 回答
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-listonce-only在 Project Alexandria中可用。

顺便说一句,Alexandria 存在一个使用define-modify-macro但需要辅助定义的removef定义。此版本不需要辅助定义。

于 2013-06-24T21:37:25.630 回答