2

While evaluating a function call, the arguments is from left to right. For example,

(let ((x '(1 2 3)))
  (list (nreverse x) x))
;; => ((3 2 1) (1))

The first argument (nreverse x) evaluates to (3 2 1), as a side effect, x is changed to (1), then the second argument x evaluates to (1). So the result is expected. However,

(let ((x '(1 2 3)))
  (list x (nreverse x)))
;; => ((1) (3 2 1))

The result is more or less surprising for me. Because the first arguments x evaluates (1 2 3), then the seconds arguments (nreverse x) evaluates to (3 2 1). The Edebug also follows the flow, thus one might get wrong assumption.

6
  • Remember that the value of x is a cons cell.
    – phils
    Nov 7, 2018 at 10:40
  • The function arguments are evaluated before the function and the list value of the symbol is more like a pointer in other languages. Therefore (nreverse x) destroys the list referenced by x. You actually have a more subtile error in your code fragments. With (let ((x '(1 2 3))) ...) you give x the constant value (1 2 3) but afterwards you manipulate that value. That leads to undefined behavior which is only by accident what you expect. The correct way is (let ((x (list 1 2 3))) ...). I had to learn it the hard way. Therefore I am telling you that.
    – Tobias
    Nov 7, 2018 at 10:41
  • @Tobias Unfortunately, I don't have much experience with other language which has pointer. About constant value, the Elisp manual of nreverse says (setq x '(a b c)) (nreverse x), should it be changed to (list 'a 'b 'c) too?
    – xuchunyang
    Nov 7, 2018 at 12:00
  • Good catch. I think that is really an error in the manual. See "The consequences are undefined if literal objects (including quoted objects) are destructively modified." at the Common Lisp Hyper Spec and that answer on SO. I believe lisp behaves the same as CL in that aspect. There was already a discussion about that here on SE. Constant lists are also special in Elisp. They modify many constant lists in the manual!
    – Tobias
    Nov 7, 2018 at 13:07
  • ... Since the Hyper Spec only says something about CL, there is not really a Spec for Elisp, and I am not an Emacs programmer but only a user of Elisp. I don't really know. It's a good question for SO.
    – Tobias
    Nov 7, 2018 at 13:12

2 Answers 2

2

That's because (list x) doesn't create a copy of x, so if you change x later, the change is reflected in the list:

(let* ((x '(1 2 3))
       (y (list x)))
  (list x y (nreverse x)))
=> ((1) ((1)) (3 2 1))
2

What @phils said in a comment: "Remember that the value of x is a cons cell."

The cons cell x is passed to list, and then it is reversed when list's second arg is evaluated. Just as you expected.

The result of list is a list of the cons cell x (which has been changed) and the result of (nreverse x).

Try this, and look at the message:

(let ((x '(1 2 3)))
  (list (progn
          (message "X: %S" x) (sleep-for 1) 
          x)
        (nreverse x)))

And BTW (for @Tobias, for example), your example has nothing to do with the result of modifying a quoted list being "undefined".

And it's not really undefined, though Common Lisp, which is a spec for any number of implementations, says it is undefined in the spec, because you cannot depend on any particular behavior about it from all implementations. And even Emacs Lisp would not want to tie its implementation hands by specifying any particular behavior for this that the designers would then have to try to maintain (for interpreter, byte-compiler, reader, etc.).

The point about not modifying a "constant" (and it's not a constant), that is, a quoted cons, is this: Do not expect that the sexp '(some list) gets re-read or re-evaluated each time, if you use it in code that is evaluated more than once. Do not expect that a new list (some list) gets created each time.

An implementation is free to create a new list (new conses) each time, or some times, or to just reuse the same list that was created during the first read/evaluation.

This is a typical gotcha. Someone writes (let ((x '(1 2 3)))...) in some context, then modifies that list structure, but expects that each time the context is used she gets a new list (new conses) bound to local variable x. Not necessarily.

The confusion comes from thinking of the program as the program text. The executing program works with things like list structure (conses), that are live. The program can modify some list structure whose apparent representation in the textual program seems to be constant/static.

2
  • The user xanthir states in his answer at stackoverflow that quoted lists may even be used in the sense of common subexpressions by the byte compiler. That way completely separate functions may reference the same constant list. If the developers of Emacs Lisp leave quoted lists unspecified anything may happen and one should use quoted lists only if one really intents to keep them constant.
    – Tobias
    Nov 7, 2018 at 15:24
  • @tobias: Agreed. Don't count on what looks like a quoted, literal list in source code being a constant. And don't count on a new list being created and returned by such source code each time it is invoked. There are a Lisp reader, an interpreter, and a byte-compiler, each of which can create (or not) new list structure (conses).
    – Drew
    Nov 7, 2018 at 15:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.