Push doesn't modify the list being a function

2019-02-26 10:41发布

问题:

I'm new to common lisp, so hope someone would clarify this to me:

say we have a list and want to add an item with push to modify it:

CL-USER> (defparameter xx '(1 2 3))
XX
CL-USER> xx
(1 2 3)
CL-USER> (push 100 xx)
(100 1 2 3)
CL-USER> xx
(100 1 2 3)

as expected. But when i try to do the same with the function, it doesn't modify a list:

CL-USER> (defun push-200 (my-list)
           (push 200 my-list))
PUSH-200
CL-USER> (push-200 xx)
(200 100 1 2 3)
CL-USER> xx
(100 1 2 3)

so i tried to compare argument and my list like this:

CL-USER> (defun push-200 (my-list)
           (format t "~a" (eq my-list xx))
           (push 200 my-list))

WARNING: redefining COMMON-LISP-USER::PUSH-200 in DEFUN
PUSH-200
CL-USER> (push-200 xx)
T
(200 100 1 2 3)
CL-USER> xx
(100 1 2 3)

it says the objects are identical. So the question is: what was the thing I've overlooked here?

回答1:

(defun push-200 (my-list)
  (push 200 my-list))

This modifies the variable my-list. The variable now points to a new cons.

It is basically: (setq my-list (cons 200 my-list).

(push-200 xx)

Common Lisp evaluates xx to a value and passes the list to push-200. xx is not passed, but the value of it. So, the Lisp system can't modify xx to point to a different cons, since it is not passed.



回答2:

This is (essentially) the same problem highlighted by the following:

(let ((a (list 2 3 4 5)))
  (let ((b a))
    (push 1 a)
    (list (eql a b)
          (eql (cdr a) b))))

This form should return (NIL T) when run.



回答3:

What Rainer Joswig writes (and Vatine demonstrates). Just replace (push <A> <B>) with (setq <B> (cons <A> <B>)) wherever you see it, because push is a macro:

"push item place => new-place-value"

And,

"new-place-value [is] a list (the new value of place)"

Notice CLHS didn't say "(the updated structure referred to by place)".

So, the value of place changes. And that place, in your case, is the local variable my-list. It was eq to xx before the (setq ...), but obviously not, after it. To actually alter a list structure you can use rplaca and ⁄ or rplacd.

We can think about argument passing in Common Lisp mostly as if what was passed is a pointer to a value; the pointer itself is passed by value, i.e. copied. So xx "points" at some list; and initially my-list "points" at the same memory location. That's why the initial eq test succeeds.