3

I just spent a significant amount of time debugging an issue that boils down to the following:

(defun my-new-alist ()
  `((x . nil)))

(setq a (my-new-alist))
(setq b (my-new-alist))

(push 1 (alist-get 'x a))
b ;; ((x . 1))

I expected a and b to be independent of each other (that is, I expected my-new-alist to be pure. Instead, it seems to create a new list when the defun is executed, and always return a reference to that very list. Part of my confusion stems from comparing with the following:

(defun my-new-list ()
  nil)

(setq a (my-new-list))
(setq b (my-new-list))

(push 1 a)
a ;; => (1)
b ;; => nil

Note also that anti-quoting the nil part fixes the issue, it seems; that is, the following function doesn't exhibit the problem:

(defun my-new-alist ()
  `((x . ,nil)))

What is the significant difference between the list and alist examples? Which parts of the ELisp manual discuss this issue? Why does antiquoting the nil part fix things?

This question is similar to, but not the same as, Proper reinitialization of a list? What is happening under the hood?. The answers to that question doesn't discuss why using nil isn't a problem, nor do they explain why antiquoting nil makes a difference. Additionally, I am looking for references in the Emacs lisp manual.

Clément
  • 3,924
  • 1
  • 22
  • 37
  • 1
    Possible duplicate of [Proper reinitialization of a list? What is happening under the hood?](http://emacs.stackexchange.com/questions/18444/proper-reinitialization-of-a-list-what-is-happening-under-the-hood) – Drew Jun 13 '16 at 03:18
  • I tried to clarify the differences. Thanks for the pointer! – Clément Jun 13 '16 at 03:23
  • Duplicate of http://stackoverflow.com/q/16670989/729907 – Drew Jun 13 '16 at 03:37
  • In elisp, `(backquote (form))` behaves the same as `(quote (form))` when there are no evaluated or spliced components. Therefore see http://emacs.stackexchange.com/q/20535 and also related Q&As http://stackoverflow.com/q/36964728 (for hash tables) and http://stackoverflow.com/q/18790192 (not elisp specific). – phils Jun 13 '16 at 03:39
  • Possible duplicate of [Why the Constant in \`let\` Designed in This way?](http://emacs.stackexchange.com/questions/20535/why-the-constant-in-let-designed-in-this-way) – phils Jun 13 '16 at 03:40
  • Your first block manipulates the same Lisp object, check this with `(eq a b)` before and after the `push`, whereas `,nil` creates different objects. IMO the duplicate answer explains that pretty well. – mutbuerger Jun 13 '16 at 03:41
  • 1
    For backquote, see the Elisp manual, node [Backquote](http://www.gnu.org/software/emacs/manual/html_node/elisp/Backquote.html). There you will see that it can quote only part of a sexp, evaluating other parts (preceded by commas). Your second backquoted sexp macroexpands to `(list (list 'x))`, and `list` creates new list structure when invoked. – Drew Jun 13 '16 at 03:55
  • If you want to see what's going on (mostly - except details of the Lisp reader), use `macroexpand` on each of the sexps you are interested in -- e.g., `(push 1 (alist-get 'x a))` – Drew Jun 13 '16 at 04:15
  • @Drew the stackoverflow question is indeed it. Is there a way to mark cross-site duplicates? – Clément Jun 13 '16 at 12:36
  • @Clément: As far as I know, no - there is apparently no problem with duplicates across sites. Cross-referencing the duplicate or similar post on another site can be helpful, at least. – Drew Jun 13 '16 at 13:14

1 Answers1

2

This does require precision to state correctly; I'll give it a go.

Whenever a form is read in, read is creating some lisp structure as it goes. The result of reading the text "(defun my-new-alist () `((x . nil)))" is a list of four elements, 'defun, 'my-new-alist, nil, and finally (quote ((x . nil))). When you eval this list, eval creates a function object using the caaar and caaaar as the argument list and the body, and assigns it to the environment using the name in the caar. This is straight forward.

Now when you call the function, it has to eval the body in the environment created by extending the environment of the call site by creating new bindings for the formal paramters in the arglist and the values from the call (this is just dynamic scope, and isn't actually relevant here, since your arglist is nil). The first thing it evaluates here is the call to the quote special form. quote is a form and not a function, because it specifically returns its argument without evaluating it, whereas all the arguments to a function call are evaluated automatically. Thus all calls to my-new-alist return a reference to the same list, in fact to the list that was created when this form was read.

Now we look at push. If we look in the ELisp manual, on the List Variables page (run M-x (info "(elisp)List Variables") to jump right to it), we see that "[push] creates a new list whose CAR is ELEMENT and whose CDR is the list specified by LISTNAME, and saves that list in LISTNAME." We also see that push is equivalent to (setf LISTNAME (cons ELEMENT LISTNAME)). In this case, that's (setf (alist-get 'x a) (cons 1 (alist-get 'x a)). This actually changes the value of the cdr in your alist to point to the newly-created cons. Anyone who has a reference to the alist will then see this new cons.

db48x
  • 15,741
  • 1
  • 19
  • 23
  • You should explain that the backquote is a macro which expands into `quote` after reading but before evaluating `defun` (actually `defun` is macro too, but you can ignore that aspect here). – npostavs Jun 13 '16 at 03:56
  • 1
    For this particular topic, I think it's important to differentiate the *lisp objects* resulting from the read phase, and the textual code which was read (and/or the printed representation of the objects). When you use (only) textual lisp code to represent the objects which will be evaluated, I don't think it's obvious why that isn't the same thing as the original text code processed by the reader. Making the distinction clarifies why certain things are *the same object* while others are not. – phils Jun 13 '16 at 04:53
  • @phils: yes, it really ought to have some box-and-pointer diagrams – db48x Jun 13 '16 at 07:15