Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

setf local variable change the value of global variable

56 views
Skip to first unread message

Jinsong Zhao

unread,
Jan 3, 2023, 3:39:53 AM1/3/23
to
Hi there,

I am very confused by the following code:

* (setf *l* '(1 2 3 4))
(1 2 3 4)
* (let ((a *l*))
(setf (car a) '(0 1)))
(0 1)
* *l*
((0 1) 2 3 4)

If I change (setf (car a) ...) to (setf a ...), *l* will not be changed.
I don't know why the value of *l* is changed.

I will appreciate any hint to reference reading.

Best,
Jinsong

Spiros Bousbouras

unread,
Jan 3, 2023, 4:13:38 AM1/3/23
to
On Tue, 3 Jan 2023 16:39:47 +0800
Jinsong Zhao <jsz...@yeah.net> wrote:
> Hi there,
>
> I am very confused by the following code:
>
> * (setf *l* '(1 2 3 4))
> (1 2 3 4)
> * (let ((a *l*))
> (setf (car a) '(0 1)))
> (0 1)
> * *l*
> ((0 1) 2 3 4)
>
> If I change (setf (car a) ...) to (setf a ...), *l* will not be changed.
> I don't know why the value of *l* is changed.

First , you are modifying a quoted object and that's undefined behaviour.
This actually has come up recently a few times in the group. So lets say
that you had instead

(setf *l* (list 1 2 3 4))
(let ((a *l*))
(setf (car a) '(0 1)))

The first form creates somewhere in memory a list with the elements 1 2 3 4
and has *l* point to that list. When you do (let ((a *l*)) ...) then within
the scope of let , a also points to the same place in memory. Then you do
(setf (car a) '(0 1)) which says "Change the first element of the list that
a points to to '(0 1) ". Since *l* also points to the same place in memory ,
when you print the value of *l* [which automatically dereferences *l* and shows
you the object that *l* points to rather than the memory address *l* holds]
, you see the changed list. If you were to do instead

(let ((a *l*))
(setf a '(0 1)))

then the Common Lisp system will create a new list with the elements 0 1
and the setf will have a point to that list. So the value of the list
that *l* points to does not get affected.

--
The gore was over the top with several zombies snacking on poor Mrs.
Menard (Olga Karlatos). At least they waited until she showered and was
nice and clean before they chowed down.
www.imdb.com/review/rw1720388/

His Kennyness

unread,
Jan 3, 2023, 8:43:38 AM1/3/23
to
Congratulations, you have reached a classic step in mastering Lisp. This addresses the issue: https://gigamonkeys.com/book/they-called-it-lisp-for-a-reason-list-processing.html

As the author says, there is no such thing as a list; the `list` function produces a linked list chain of `cons` cells, structures with two famous parts, the `car` and the `cdr`.

So *i* does not really hold the whole list, it just holds a pointer to the first cons cell.

Likewise, `(let [a *i*]....)` does not make `a` into a copy of the "list" pointed to by *i*, it just makes `a` into a copy of the same _pointer_ (to the same cons cell as *i*).

That ^^ explains why changing `(car a)` also changes `(car *i*)`

> > If I change (setf (car a) ...) to (setf a ...), *l* will not be changed.

You did not show all your code there, so it is hard to answer except to say `(setf a ...)` certainly changes `a` from being the same as *i* to being something different, and thus leaving *i* alone. I hesitate here because we are seeing that, in Lisp, we can indeed alter existing cons cells, so if the ellipsis in `(set a ...)` included a mutating action on the chain of cons cells pointer to by *i*, we could still end up "changing" *i*.

tl;dr: resist the syntactic sugar that is the "list" concept, and remember to think about chains of cons cells, each an object with identity, and each with two parts, a car and a cdr.

Tom Russ

unread,
Jan 3, 2023, 4:52:06 PM1/3/23
to
On Tuesday, January 3, 2023 at 12:39:53 AM UTC-8, Jinsong Zhao wrote:
> Hi there,
>
> I am very confused by the following code:
>
> * (setf *l* '(1 2 3 4))
> (1 2 3 4)
> * (let ((a *l*))
> (setf (car a) '(0 1)))
> (0 1)
> * *l*
> ((0 1) 2 3 4)
>
> If I change (setf (car a) ...) to (setf a ...), *l* will not be changed.
> I don't know why the value of *l* is changed.

Other posters have explained that you are modifying the underlying list structure.
That is one of the traps of allowing SETF to modify parts of lists like (CAR A).*

Assuming that you don't want to have this effect, there are a couple of alternatives:

1. Make a copy of the list. Bindings and assignment of lists just involve pointers,
so if you want a copy you need to be explicit. This will let you modify a copy of the
list structure.

. (let ((a (copy-list *I*)))
. (setf (car a) '(0 1)))

Now COPY-LIST will only copy the top-level "backbone" of the list. If you want to
have a complete copy that doesn't share any cons cells you need to use COPY-TREE
instead.

2. Don't use destructive list operations. For your case that would look like

. (let ((a *I*))
. (setf a (cons '(0 1) (cdr a))))

This builds new list structure (via CONS) rather than modifying the existing list structure.
It is important to remember, though, that now A and *I* share some list structure, namely
the CDR of the original list or (2 3 4).
You can test this using the object equality predicate EQ: **
(EQ (CAR A) (CAR *I*)) ==> NIL
(EQ (CDR A) (CDR *I*)) ==> T
(EQ (CDDR A) (CDDR *I*)) ==> NIL


* Before lisp had that support, you would have had to call a more cumbersome function
RPLACA (short for "replace car") which was a little more likely to warn you that you were
mutating the list structure rather than just assigning variables.

** But you have to be a bit careful about EQ, as it can be tricky. For example
(EQ 3 3) may be either T or NIL depending on details of the implementation.
http://www.lispworks.com/documentation/HyperSpec/Body/f_eq.htm

Spiros Bousbouras

unread,
Jan 3, 2023, 11:10:32 PM1/3/23
to
On Tue, 3 Jan 2023 13:52:03 -0800 (PST)
Tom Russ <tar...@google.com> wrote:
> 2. Don't use destructive list operations. For your case that would look like
>
> . (let ((a *I*))
> . (setf a (cons '(0 1) (cdr a))))
>
> This builds new list structure (via CONS) rather than modifying the existing
> list structure.
> It is important to remember, though, that now A and *I* share some list
> structure, namely
> the CDR of the original list or (2 3 4).
> You can test this using the object equality predicate EQ: **
> (EQ (CAR A) (CAR *I*)) ==> NIL
> (EQ (CDR A) (CDR *I*)) ==> T
> (EQ (CDDR A) (CDDR *I*)) ==> NIL

3rd line should return T .

Tom Russ

unread,
Jan 5, 2023, 1:47:13 PM1/5/23
to
Correct. Thanks.

Kaz Kylheku

unread,
Jan 6, 2023, 12:38:30 PM1/6/23
to
On 2023-01-03, Jinsong Zhao <jsz...@yeah.net> wrote:
> Hi there,
>
> I am very confused by the following code:
>
> * (setf *l* '(1 2 3 4))
> (1 2 3 4)
> * (let ((a *l*))
> (setf (car a) '(0 1)))
> (0 1)
> * *l*
> ((0 1) 2 3 4)
>
> If I change (setf (car a) ...) to (setf a ...), *l* will not be changed.
> I don't know why the value of *l* is changed.

Lists (other than the empty list nil) are made of binary pair objects
called conses. A list value is a pointer/reference to a cons whose car
holds the first item, and whose cdr holds the rest of the list:
another cons, or perhaps nil if there are no more items.

When you bind the variable a to *l*, a points to the same cons cell as
*l*. The list object has not been duplicated, only the pointer.

To understand computer programming to the fullest extent, you have to
become very familiar with pointers, because they are everywhere. Not
only machine address pointers, but pointer-like entities: symbolic links
and hard links in file systems, WWW hyperlinks, parent pointers in git
commits, object IDs in databases, ...

A key question in computer science (and mathematics) is what makes two
things X and Y "same", and there can be more than one valid answer.

E.g. two character strings X and Y can be "same" if they have the same
characters, like "abc" and "abc". But it's possible that X and Y,
if they are the same, are actually the same object; i.e. X and Y
are just different names referring to the same entity.

Under the Law of Identity (https://en.wikipedia.org/wiki/Law_of_identity)
a thing X should always be the same as itself. The people who designed
IEEE 754 floating-point disagreed: a NaN value is not equal to itself,
even if it's the same one (same bit pattern).

On the other hand you can have a sameness notion such that, say, "John"
and "JOHN" are considered the same. Those specific two wouldn't
be the same object, but this kind of case-insensitive sameness
is compatible with the Law of Identity; "John" is still "John".

When a variable in some programming language is copied from another, we
expect the value of the copy and original to be the same (with odd
exceptions like floating-point NaN values). That sameness could be due
to the values being in fact references to the same entity; you always
have to ask that question and suspect it, almost no matter what
language you are working in.





--
TXR Programming Language: http://nongnu.org/txr
Cygnal: Cygwin Native Application Library: http://kylheku.com/cygnal
Mastodon: @Kazi...@mstdn.ca
0 new messages