13

Say we have such a foo function:

(defun foo (e)
  (let ((lst '(a b c)))
    (delq e lst)))

Then we use it in following way (sequentially evaluating one by one):

(foo 'c) ; => (a b)
(foo 'b) ; => (a)
(foo 'a) ; => nil
(foo 'b) ; => (a)

What happened here?

If I define another bar function:

(defun bar (e)
  (let ((lst (list 'a 'b 'c)))
    (delq e lst)))

Then use it in the same way:

(bar 'c) ; => (a b)
(bar 'b) ; => (a c)
(bar 'a) ; => (b c)
(bar 'b) ; => (a c)

The results here seem reasonable to me.

So question is, why the constant in the let form of the foo function behave in this way? Is it intended?

6
  • 3
    This is a dupe of stackoverflow.com/q/16670989/850781
    – sds
    Feb 23, 2016 at 23:37
  • 2
    Another key quotation highlighted at the duplicate on S.O. is this (which is from C-h f quote): "Warning: quote does not construct its return value, but just returns the value that was pre-constructed by the Lisp reader". I think that newcomers to lisp are frequently oblivious to the whole concept of the lisp reader, and so taking some time to learn and understand the distinction between the "read" and "eval" stages is very beneficial to understanding the language in general, as well as specific issues like the one above.
    – phils
    Feb 24, 2016 at 0:06
  • @phils Do you mean that ' is a reader macro, which is processed in "read" stage, while (list 'a 'b 'c) is evaluated in the "eval" stage?
    – cutejumper
    Feb 24, 2016 at 1:53
  • 1
    Strictly speaking ' isn't a reader macro in elisp (elisp doesn't have reader macros), but it certainly is processed during the read phase. That's not the important thing, though. My point was about understanding that the reader transforms all of the textual lisp code into lisp objects (once), and it is those objects that are subsequently eval'd (often repeatedly), rather than the text that you see.
    – phils
    Feb 25, 2016 at 10:27
  • 1
    And because objects are potentially subject to manipulation during evaluation (as in this question), what's being eval'd at any given time doesn't need to represent the code that was originally read. Of course in most circumstances it will do exactly that -- you don't often need to think in terms of objects. But knowing what's going on behind the scenes allows you to recognise and understand the situations in which this sort of thing is a factor.
    – phils
    Feb 25, 2016 at 10:33

1 Answer 1

22

Here is my answer to the identical question, appropriately edited:

The Bad

foo is self-modifying code. This is extremely dangerous. While the variable lst disappears at the end of the let form, its initial value persists in the function object, and that is the value you are modifying. Remember that in Lisp a function is a first class object, which can be passed around (just like a number or a list), and, sometimes, modified. This is exactly what you are doing here: the initial value for lst is a part of the function object and you are modifying it.

Let us actually see what is happening:

(symbol-function 'foo)
==> (lambda (e) (let ((lst (quote (a b c)))) (delq e lst)))
(foo 'c)
==> (a b)
(symbol-function 'foo)
==> (lambda (e) (let ((lst (quote (a b)))) (delq e lst)))
(foo 'b)
==> (a)
(symbol-function 'foo)
==> (lambda (e) (let ((lst (quote (a)))) (delq e lst)))
(foo 'a)
==> nil
(symbol-function 'foo)
==> (lambda (e) (let ((lst (quote (a)))) (delq e lst)))

The Good

In bar, lst is initialized to a fresh cons cell and thus is safe. bar does not modify its code.

The Bottom Line

In general, it is best to treat quoted data like '(1) as constants - do not modify them:

quote returns the argument, without evaluating it. (quote x) yields x. Warning: quote does not construct its return value, but just returns the value that was pre-constructed by the Lisp reader (see info node Printed Representation). This means that (a . b) is not identical to (cons 'a 'b): the former does not cons. Quoting should be reserved for constants that will never be modified by side-effects, unless you like self-modifying code. See the common pitfall in info node Rearrangement for an example of unexpected results when a quoted object is modified.

If you need to modify a list, create it with list or cons or copy-list instead of quote, like you do in bar.

See more examples.

ps - variable naming

Emacs Lisp is a lisp-2 so these is no reason to name your variable lst instead of list.

2
  • 2
    Thanks for the detailed explanation! Also, I know ELisp is lisp-2 but still, I don't want to confuse myself. Something like (list (list 'a 'b 'c))(although it is in the let form) looks very confusing at the first sight.
    – cutejumper
    Feb 25, 2016 at 21:59
  • yes that is very confusing! But also really interesting, I had no idea of lisp-1 nad lisp-2s.
    – Rorschach
    Jul 3, 2016 at 6:00

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