修改函数的参数

时间:2013-06-13 15:00:57

标签: lisp common-lisp

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

2 个答案:

答案 0 :(得分:7)

在Common Lisp中,参数是“通过身份”传递的(this term goes back to D. Rettig,是Allegro Common Lisp实现的开发人员之一)。想想通过值传递的指针(对堆对象),对于大多数Lisp对象(如字符串,向量,当然还有列表)都是如此;事情稍微复杂一些,因为实现也可能具有直接值,但是旁边这一点)。

setf的{​​{1}}修改了函数的(私有,词法)变量绑定。此更改在seq之外不可见。

为了使removef能够在通话时影响周围环境,您需要将其设为宏:

removef

您可能需要查看setfgeneralized 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-listonce-onlyAlexandria

BTW存在于Alexandria removef定义,使用define-modify-macro但需要辅助定义。此版本不需要辅助定义。