2

I have the following function (simplified to that version):

(defun append-test (xs)
 (let ((ys `(foo ,(nconc `(bar) xs))))
    (nconc ys `((baz)))))

If I evaluate the function multiple times (append-test '((foo))) the list keeps increasing in size, but I can't understand why. The nconc modifies the original list, but because the original list is either a function argument or let-variable, they should be created anew per every invocation, right? What is happening here?

Masse
  • 4,334
  • 3
  • 30
  • 41
  • possible duplicate of [Why does an elisp local variable keep its value in this case?](http://stackoverflow.com/questions/16670989/why-does-an-elisp-local-variable-keep-its-value-in-this-case) – sds Aug 06 '14 at 13:37

2 Answers2

1

If you change `(bar) to (list 'bar), then the code returns the same result every time.

nconc modifies all arguments but the last one. Obviously, if the first argument were '(bar), then the quoted list contained within the function definition would be modified, and we would expect to see the result you see. Apparently using a backquote expression that doesn't contain any commas is equivalent to using a quoted list.

Actually, the following piece of code keeps the backquote, but adds a pointless expression to the cdr of the list. Apparently, this makes it allocate a new list every time the function is called, and thus it returns the same result every time:

(defun append-test (xs)
 (let ((ys `(foo ,(nconc `(bar . ,(ignore)) xs))))
    (nconc ys `((baz)))))
legoscia
  • 39,593
  • 22
  • 116
  • 167
  • Could you elaborate a little? xs is an argument, ys is let-variable and rest of the lists are function returned values (quote or list). All of these should be 'new' per function call, but clearly some of these are shared and modified by nconc – Masse Aug 06 '14 at 10:51
  • 1
    The thing that's shared is the `(bar)` list inside the first `nconc` call. It is conserved as a quoted value inside the function definition, and its value is therefore mutated every time by the `nconc` call. – legoscia Aug 06 '14 at 11:00
  • 4
    @Masse Don't confuse allocation with binding. `let` *binds*, but it does not allocate new storage on its own. Allocation depends on the right hand side of the binding, and quoted lists are allocated *statically* by the reader, so your `let` binds to the *same*, statically allocated list on each call. Consider the following, simpler example: https://gist.github.com/lunaryorn/5196701576705458ecee –  Aug 06 '14 at 11:04
1

I was bitten by this. Here are some bugs that resulted.

http://comments.gmane.org/gmane.emacs.bugs/50783

http://lists.gnu.org/archive/html/emacs-bug-tracker/2011-09/msg00220.html

This issue seems to be a rite of passage for lisp developers. :-)

From what I understand, lisp evaluation is split in two phases:

  1. Read : A reader that statically allocates storage and reads forms into memory.
  2. Evaluate : The read form is evaluated.

What about quote?

quote simply returns the read form without evaluation.

Implicitly,

  1. When a quote form is evaluated, it just returns the reader allocated object directly.
  2. When a quote form is re-evaluated (i.e. function call, etc), it returns the same reader allocated object.
  3. quote does not allocate new memory

Ta-da!

event_jr
  • 17,467
  • 4
  • 47
  • 62