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.
Your first snippet works like this:
(defun to-temp-field (l)
L is a variable, supposed to have a list as its value
(let ((old-value (nth 2 l))
OLD-VALUE is a variable and is initialized to the third element of the list L
(new-value 'hello))
NEW-VALUE is a variable and is initialized to the symbol HELLO
(setf old-value new-value)
the variable OLD-VALUE is set to the value of the variable NEW-VALUE -> HELLO
l))
L is returned, unchanged.
The second snippet works like this:
(defun to-temp-field (l)
L is a variable, supposed to have a list as its value
(let ((new-value 'hello))
NEW-VALUE is a variable and is initialized to the symbol HELLO
(setf (nth 2 l) new-value)
SETF changes the place (NTH 2 L) to the value of the variable NEW-VALUE. This place denotes the third element of the list L. The list is changed, it now has the symbol HELLO as its third element.
l))
L is returned, changed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With