1

I was surprised to discover that a local variable can have it's state modified across function invocations. Consider the following example.

(defun example-mutable ()
  (let ((x '("the" "quick" "brown" "fox")))
    (nbutlast x 1)))

nbutlast is documented as follows:

This is a version of butlast that works by destructively modifying the cdr of the appropriate element, rather than making a copy of the list

(And butlast is a function removes the last n elements from a list.)

Then multiple calls to example-mutable will return ("the" "quick" "brown"), ("the" "quick"), ("the"), and nil, in that order.

Another example is the following:

(defun example-nonmutable (change-val)
  (let ((x '("the" "quick" "brown" "fox")))
    (when change-val
      (setq x "something else"))
    x))

Then calling (example-nonmutable t) followed by (example-nonmutable nil) yields results of "something else" and ("the" "quick" "brown" "fox"). So we see that in the first example the value of x is not rebound on repeat function invocations, while contrastingly in the second example it is rebound.

So I am guessing that when the function is called that Emacs looks at the address of the data that x is bound to, and if it hasn't been changed, then it doesn't bother to create a new data object and rebind it in the interest of efficiency?

Is this behavior documented in the manual or elsewhere?

dpritch
  • 425
  • 5
  • 7

1 Answers1

2

A quoted list is a constant thus should not be modified, if you modify a constant inside a function, the function will be self-modifying code, for example,

;; Return how many this function is called
(defun counter ()
  (cl-incf (car '(0))))

(counter)
;; => 1

(counter)
;; => 2

(counter)
;; => 3

The function returns a different value each time it runs, because we are modifying the quoted constant list, '(0), thus the function definition is keeping changing

(symbol-function 'counter)
;; => (lambda nil (setcar (quote (3)) (+ (car (quote (3))) 1)))

(counter)
;; => 4

(symbol-function 'counter)
;; => (lambda nil (setcar (quote (4)) (+ (car (quote (4))) 1)))

According to my experience, it's fine to modify a quoted constant list outside a function, and you can see a lot of them in the elisp manual, e.g.,

(setq l '(1 2 3))
;; => (1 2 3)

(setcar l 100)
;; => 100

(let ((l '(1 2 3)))
  (cl-incf (car l))
  l)
;; => (2 2 3)

Not only quoted constant list, you should also not modify literal vector and string, such as, [1 2 3] and "hello". If you need to modify a list or vector, you need use (list 1 2 3) and (vector 1 2 3), instead of '(1 2 3) and [1 2 3].

Related question: When to use quote for lists? Modifying quoted lists in Elisp

xuchunyang
  • 14,302
  • 1
  • 18
  • 39