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?
setftakes 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-valueis a lexical variable bound within theletform. Lexical variables are places, so calling(setf old-value new-value)sets the value ofold-valuetonew-value.Function forms can also be places, and
nthis 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 valuenew-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 listlst) is updated.To be clear: a place is not a value, it is a form.
With
(let ((old-value (nth 2 lst)) ;;...))old-valueis 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-valueis a form, and a place. Here the variableold-valuehas 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 tosetfby mutating the binding ofold-value. As a result the value of the placeold-valuehas been changed.A few things to note:
The second example updates the third element of
lstsince lists are zero-indexed in Common Lisp.There is no need for the
prognform inside ofletsince the body of aletform is an implicitprogn.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.