2

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?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
leetwinski
  • 17,408
  • 2
  • 18
  • 42
  • 3
    Push isn't supposed to modify the list; it's supposed to modify the place to update the place's value to be a new list. The place, in this case, is a local variable, and the local variable *is* updated to hold a new list. – Joshua Taylor Jan 14 '16 at 15:24
  • 2
    You may find [Common lisp push from function](http://stackoverflow.com/questions/16396224/common-lisp-push-from-function) helpful. – Joshua Taylor Jan 14 '16 at 15:26
  • Thanks! now i see the semantics, but still it looks weird to me. So it means the only way to `push` some val to list inside a function, is to modify it's `car/cdr` ? like `(let ((old-car (car my-list))) (setq (car my-list) value (cdr my-list (cons old-car (cdr my-list))))` ? – leetwinski Jan 14 '16 at 15:38
  • What you're asking for isn't really idiomatic in Common Lisp. Functions that modify list structure (e.g., **delete**, **sort**) typically *return* a result, and it's up to the consumer to *use* the result. Functions aren't usually called for destructive side-effects; rather the side-effects are *permitted* for efficiency, but the contract ("use the result") stays the same. – Joshua Taylor Jan 14 '16 at 15:44
  • 1
    Your approach to "push some val to a list inside a function" approach doesn't work; what happens if the original list is empty? You can't modify the car or cdr of the empty list; it doesn't have those. That said, if you do want to use the approach you described, you might do it a little more clearly with `(push newvalue (rest list)) (rotatef (first list) (second list))`. (That's certainly not the only option, though.) – Joshua Taylor Jan 14 '16 at 15:45
  • Thanks again. Ok, i'm aware of this style (quasi functional, i would say), just wanted to clear out the semantics. But this obviously leads me to another, more significant question: is there any proper styleguide for modern common lisp development? I mean the language is huge, and i'm always paralyzed about what solution to chose. – leetwinski Jan 14 '16 at 20:52
  • I don't really see this as a *style* issue; it's just one of data structures. Aside from C/assembler where you can pass the *address* of primitive structures around, in pretty much *any* language you'll need to pass some kind of value container if you want to update the value. E.g., in Java the List that you pass into such a function can have elements added to it because the List *encapsulates* the underlying list of values. In Common Lisp, you could certainly do `(defstruct lyst x)` and `(defun push-200 (lyst) (push 200 (list-x lyst)))`, and get the behavior that you're looking for. – Joshua Taylor Jan 14 '16 at 20:58

3 Answers3

3
(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.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
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.

Vatine
  • 20,782
  • 4
  • 54
  • 70
1

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.

Will Ness
  • 70,110
  • 9
  • 98
  • 181