5

The code below has z as a local variable, yet it behaves as if it is a global:

(defun foo (m)
  (let ((z '(stuff nil)))
    (push m (getf z 'stuff))
    (print z)))

(foo 1)
(foo 2)
(foo 3)

I would expect the output to be

(STUFF (1)) 
(STUFF (2)) 
(STUFF (3)) 
T

but when running it with SBCL I see

(STUFF (1)) 
(STUFF (2 1)) 
(STUFF (3 2 1)) 
T

Why is this the case? Is this behaviour peculiar to property lists?

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • Possible duplicate of [Unexpected persistence of data](http://stackoverflow.com/questions/18790192/unexpected-persistence-of-data) – Joshua Taylor Oct 23 '15 at 20:06

1 Answers1

6

In foo, z is bound to the literal expression '(stuff nil). The function destructively alters z, thus destructively changing the value of the literal. How LISP behaves in circumstances like this is implementation-dependent. Some implementations will obediently alter the literal value (as in your case). Other implementations place literals in read-only memory locations and will fail if you attempt to modify those literals.

To get the desired behaviour, use COPY-LIST to make a copy of the literal that can be safely modified:

(defun foo (m)
  (let ((z (copy-list '(stuff nil))))
    (push m (getf z 'stuff))
    (print z)))
WReach
  • 18,098
  • 3
  • 49
  • 93
  • 7
    I think the more idiomatic way would be to use `LIST`, as in `(let ((z (list 'stuff nil))) ...)` – Ken Jan 08 '11 at 06:51