Modify a parameter of a function

2019-02-25 00:36发布

(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)

Why doesn't removef really modify the variable?

2条回答
Emotional °昔
2楼-- · 2019-02-25 01:14

In Common Lisp, parameters are passed "by identity" (this term goes back to D. Rettig, one of the developers of the Allegro Common Lisp implementation). Think of pointers (to heap objects) being passed by values, which is true for most Lisp objects (like strings, vectors, and, of course, lists; things are slightly more complicated, since implementations might also have immediate values, but that's beside the point here).

The setf of seq modifies the (private, lexical) variable binding of the function. This change is not visible outside of removef.

In order for removef to be able to affect the surrounding environment at the point of the call, you need to make it a macro:

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

You might want to take at look at setf and the concept of generalized references. Note, that the macro version of removef I provided above is not how it should actually be done! For details, read about get-setf-expansion and its ugly details.

If all you want to do is to destructively modify the list, consider using delete instead of remove, but be aware, that this might have unintended consequences:

(delete 2 '(1 2 3 4))

is not allowed by the ANSI standard (you are destructively modifying a literal object, i.e., part of your code). In this example, the mistake is easy to spot, but if you are 7 frames deep in some callstack, processing values whose origin is not entirely clear to you, this becomes a real problem. And anyway, even

(setf list (list 1 2 3 4))
(delete 1 list)
list

might be surprising at first, even though

(setf list (list 1 2 3 4))
(delete 2 list)
list

seems to "work". Essentially, the first example does not work as intended, as the function delete has the same problem as your original version of removef, namely, it cannot change the caller's notion of the list variable, so even for the destructive version, the right way to do it is:

(setf list (delete 1 (list 1 2 3 4)))
查看更多
Fickle 薄情
3楼-- · 2019-02-25 01:29

Here is an example of an implementation of removef that is "able to affect the surrounding environment at the point of the call", as stated by @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)))))

The utilities length= , make-gensym-list and once-only are available at Project Alexandria.

BTW exists at Alexandria a removef definition that uses define-modify-macro but requires an auxiliary definition. This version does not requires an auxiliary defintion.

查看更多
登录 后发表回答