9

I'm teaching myself some more elisp and have encountered the following problem:

If I want to reset a list variable it won't get updated after the first evaluation. Here is some example code:

(defun initilize ()
  (setq example '(3)))

(defun modify ()
  (initilize)
  (message "%S" example)
  (setcar example 2))

; M-x eval-buffer RET
(modify) ; message --> (3)
(modify) ; message --> (2)
(modify) ; message --> (2)

I'm interested in two things. The first is to learn more about what is happening "under the hood" so why does it work the first time and fails on subsequent calls?

The second and more practical question is how to reinitialize the list properly or is there another common way of doing something like that?

One workaround I found myself is to use a quoted list and evaluating the content like this:

(setq example `(,3)) 
Drew
  • 75,699
  • 9
  • 109
  • 225
clemera
  • 3,401
  • 13
  • 40
  • 2
    Summary: Do not expect `'(some list)` to be `eq` to `'(some list)` - *ever*.There is generally no guarantee in Lisp that code that visibly quotes a list returns new list structure each time. In some Lisp implementations it might, or it might some of the time. In others, it never does. Your code should anyway not depend on any such behavior from the implementation. **If you want new list structure, use `list` or `cons` or equivalent.** – Drew Nov 27 '15 at 16:01
  • (I'm guessing that this question is a duplicate, but I don't know where the duplicate is.) – Drew Nov 27 '15 at 16:02
  • 1
    I think that the problem here is that `example` has never been declared as a variable, so `setq` must be acting as if it declares a new variable, but later on when you call `initialize` again a new variable is being created, while `modify` remembers the old one... in any case this is not an expected behavior, however, the use of `setq` with something that hasn't been introduced earlier as a variable might as well be undefined. – wvxvw Nov 27 '15 at 18:55
  • @wvxvw It happens when you declare `example` with defvar, too. – clemera Nov 28 '15 at 10:05
  • 3
    OK, I figured out what happens. `'(3)` is treated as a literal value, so once you `(setcar '(3) 2)`, whenever you do `(defvar foo '(3))` or `(let ((foo '(3)))` and so on you will likely get a value of `foo` equal to `'(2)`. I say "likely" because this behavior isn't guaranteed, it's a kind of optimization that the interpreter does whenever it feels like, something known as constants subexpression elimination (a particular case of). So, what **abo-abo** wrote is not exactly the reason. It's more like modifying a string literal in C (which typically generates a warning). – wvxvw Nov 28 '15 at 13:10
  • I knew this is the case in Common Lisp, but never thought that Emacs Lisp also does this. – wvxvw Nov 28 '15 at 13:11
  • 2
    Duplicate of http://stackoverflow.com/q/16670989 – phils Nov 29 '15 at 03:30

2 Answers2

5

You can use (setq example (list 3)) to avoid this error.

What happens is init assigns an object that initially contains (3) to example. It sets the value of the object only once. Subsequently, you modify this value.

Here's your example in C++, if you understand that better:

#include <stdio.h>
#include <string.h>
char* example;
char* storage = 0;
char* create_object_once (const char* str) {
  if (storage == 0) {
    storage = new char[strlen (str)];
    strcpy (storage, str);
  }
  return storage;
}
void init () {
  example = create_object_once ("test");
}
void modify () {
  init ();
  printf ("%s\n", example);
  example[0] = 'f';
}
int main (int argc, char *argv[]) {
  modify ();
  modify ();
  modify ();
  return 0;
}
abo-abo
  • 13,943
  • 1
  • 29
  • 43
  • 1
    Thanks, I don't know C++ but I understand your example. One thing still bothers me though: In your code example you introduce the variable storage which creates the object with the initial value only if it was not set yet. How does that translate to what the Emacs Lisp Interpreter is doing? I mean if I reevaluate the `initilize` function and call `modify` again it will show `(3)` again only because I reevaluated the function. – clemera Nov 27 '15 at 12:00
  • 1
    I can't get into specifics (don't exactly know them), but think of `(3)` as a temporary object that's part of `init`. `init`'s body sets `example` to the address of that temporary object, it doesn't touch its value. – abo-abo Nov 27 '15 at 12:04
5

Maybe this will clear up some of the confusion:

  • Your function initilize does not initialize variable example. It sets it to a particular cons cell - the same cons cell each time it is called. The first time initilize is called, the setq assigns example to a new cons cell, which is the result of evaluating '(3). Subsequent calls to initilize just reassign example to the same cons cell.

  • Since initilize just reassigns the same cons cell to example, modify just sets the car of that same cons cell to 2 each time it is called.

  • To initialize a list, use list or cons (or an equivalent backquote sexp, such as `(,(+ 2 1)) or `(,3)). For example, use (list 3).

The key to understanding this is to know that a quoted cons cell is evaluated only once, and thereafter the same cons cell is returned. This is not necessarily the way that all Lisps behave, but it is how Emacs Lisp behaves.

More generally, the behavior of evaluating a quoted mutable object is implementation dependent, if not language dependent. In Common Lisp, for example, I'm pretty sure that there is nothing in the definition (spec) of the language that defines the behavior in this regard - it is left up to the implementation.

Summary: Do not expect '(some list) to be eq to '(some list) - ever.There is generally no guarantee in Lisp that code that visibly quotes a list returns new list structure each time. In some Lisp implementations it might, or it might some of the time. In others, it never does. Your code should anyway not depend on any such behavior from the implementation. If you want new list structure, use list or cons or equivalent.

Drew
  • 75,699
  • 9
  • 109
  • 225
  • 1
    Is the condescending tone in the two first lines of your answer necessary? Otherwise an enlightening answer. – asjo Nov 29 '15 at 01:18
  • @asjo: The intention wasn't to condescend in any way; sorry. Hopefully it is clearer now. – Drew Nov 29 '15 at 06:05
  • Thanks to clear things up a bit. With the term "argument" I meant the argument `(,3) for the setq function, although I understand it is a bit unclear because I'm really evaluating the 3 in the quoted list. I will edit that. – clemera Nov 29 '15 at 10:23
  • @Drew Great! It's friendlier to read now. – asjo Nov 29 '15 at 17:28