2016-05-23 Literal values and destructive functions

In Lisp literature you can often find a warning about not using destructive functions (like sort or nconc) on values coming from literals. This is a good advice, and you should follow it. Here’s a short stab at a (partial) explanation (note that all examples are in Emacs Lisp).

Look at this, innocent-looking code:

(defun destructive-havoc ()
  "Example of destructive havoc."
  (setq foo '(1 3 2))
  (message "before sort, foo is: %s" foo)
  (sort foo #'<)
  (message "after sort, foo is: %s" foo))

Evaluate this defun form and try M-: (destructive-havoc). (You will need to go to the *Messages* buffer to see the actual output of the message calls.) Here it is:

before sort, foo is: (1 3 2)
after sort, foo is: (1 2 3)

Everything is proceeding as we have foreseen. But let’s pull the rabbit out of the hat now: evaluate (destructive-havoc) again. Here’s what you get:

before sort, foo is: (1 2 3)
after sort, foo is: (1 2 3)

What’s going on?

Well, the literal in the function definition was actually changed. (If you evaluate the defun form now, it will be redefined once again to the “correct” value.) If you don’t believe it, try this: M-: (symbol-function #'destructive-havoc), or even better, M-x pp-eval-expression RET (symbol-function #'destructive-havoc) RET and see for yourself.

Of course, if you really need such a list in your code, the remedy is clear: just don’t say '(1 3 2) but (list 1 3 2) in your code. This way, the list will be created again from scratch every time you invoke the function.

I’m wondering why this happens so. My first guess was that the literal lists like this get assigned a place in memory at the time of evaluating the defun, and then sort happily changes that exact place in memory. (Of course, byte-compiling the whole thing does not change the behavior, in case you wondered.)

Actually, defun has nothing to do with it; it seems to me that the moment when the thing gets its place is reading. Here’s something that seems to confirm my suspicion: I can do away with the defun altogether, and use fset and lambda instead (this is more or less what defun does internally, only it uses defalias instead of fset; defalias is a bit higher-level wrapper around fset).

(fset 'destructive-havoc
  (lambda ()
		"Example of destructive havoc."
		(setq foo '(1 3 2))
		(message "before sort, foo is: %s" foo)
		(sort foo #'<)
		(message "after sort, foo is: %s" foo)))

I don’t have time (nor courage, frankly) now to try to look at the C source to try to confirm my suspicions, but it is not that important. What is important is the takeaway of this post: never, ever use literal values in your code if you are going to modify them later. You’re welcome.

CategoryEnglish, CategoryBlog, CategoryEmacs