The behavior of setf
when combined with let
is confusing to me. Here, setf
isn't changing the second value of my list.
(defun to-temp-field (lst)
(let ((old-value (nth 2 lst))
(new-value 'hello))
(progn
(setf old-value new-value)
lst)))
But if I do it without let
, it changes ok:
(defun to-temp-field (lst)
(let ((new-value 'hello))
(progn
(setf (nth 2 lst) new-value)
lst)))
What's causing this behavior?
setf
takes a place and a value, or a number of place/value pairs, and mutates the value of the place to the new value.
In the first code example old-value
is a lexical variable bound within the let
form. Lexical variables are places, so calling (setf old-value new-value)
sets the value of old-value
to new-value
.
Function forms can also be places, and nth
is one function that can specify a place. In the second code example calling (setf (nth 2 lst) new-value)
sets the value of the place (nth 2 lst)
to the value new-value
.
So in the first example the place old-value
, a lexical variable, is updated. But in the second example the form (nth 2 lst)
is a place, and the object which this form evaluates to (an element of the list lst
) is updated.
To be clear: a place is not a value, it is a form.
With (let ((old-value (nth 2 lst)) ;;...))
old-value
is bound to the value to which the form (nth 2 lst)
evaluates. But with (setf (nth 2 lst) new-value)
the form (nth 2 lst)
is interpreted as a place. (setf old-value new-value)
works the same way: old-value
is a form, and a place. Here the variable old-value
has been let-bound to the value of the form (nth 2 lst)
, and it is this value which is replaced via assignment in the call to setf
by mutating the binding of old-value
. As a result the value of the place old-value
has been changed.
A few things to note:
The second example updates the third element of lst
since lists are zero-indexed in Common Lisp.
There is no need for the progn
form inside of let
since the body of a let
form is an implicit progn
.
Attempting to modify a literal causes undefined behavior in Common Lisp, so you shouldn't call the second function with a quoted list like this: (to-temp-field '(1 2 3))
or with a variable bound to a list literal, but rather like this: (to-temp-field (list 1 2 3))
or with a variable bound to a list that has been otherwise constructed.