Currently learning common lisp, following Peter Seibel's Practical Common Lisp (i'm at chapter 11, about collections), i have difficulties to understand how setf
works behind the hood.
Considering this expression :
(setf a 10)
I completely understood how the interpreter can (1) retrieve the variable named a
, and (2) change the value it points to to 10
.
Now, i case of particular collections, for instance lists, vectors or hash tables, setf can also be used to change values contained by the collection. For instance, with a vector :
(defparameter *x* '(a b c d))
(setf (elt *x* 1) bb)
This makes me suspicious about setf
, because it is eventually finding non-trivially accessible information, or making black magic. I see multiple possibilities.
1. setf is a function
The (elt *x* 1)
expression is returning 'b
, so setf
is virtually working with (setf b bb)
. I then do not understand how setf
can infer which object (here, the list *x*
) it must modify, without having the return value of elt
holding both an indication that it comes from a collection, and a pointer to the said collection.
Seems complicated.
2. setf is a macro
The idea is, as setf
is a macro, it works directly with the (setf (elt *x* 1) bb)
and therefore can extract the elt *x* 1
part to infer which object/collection is used, and therefore must be modified.
It do not seems very efficient, nor reliable, nor resistant to complex operations. However, since i'm unable to run this code :
(funcall (find-symbol (concatenate 'string "E" "LT")) *x* 1) ; -> B
(setf (funcall (find-symbol (concatenate 'string "E" "LT")) *x* 1) 'bb) ; -> ERROR : (SETF FUNCALL) is only defined for functions of the form #'symbol
This make me think that setf
is a macro implementing a very simple heuristic to retrieve the function to call, and all other needed information.
Seems complicated.
3. setf is a special case of interpretation
Another way to go could be to have setf be handled differently by the interpreter itself, dealing with some black magic to implement properly the expected behavior. Seems complicated.
4. there is something i do not know about lisp
Probably the real answer. What did i miss ?
Bonus question : is the implementation method dependent of the lisp interpreter implementation ? (or, more simply, what exactly the common lisp standard define about setf implementation) I'm currently using clisp but insights on others implementations are welcome.
SETF
is a macro that sets a value to a place. A place means a form that has a setf expansion. There are various kinds of places built-in, and you can define more (see for exampleDEFSETF
andDEFINE-SETF-EXPANDER
, function call forms as places and macro forms as places).You can get the setf expansion for a form using
GET-SETF-EXPANSION
. It returns five values. For example,The fifth value is a getter form that, when evaluated, returns the current value of the place. The fourth one is a setter form that, when evaluated, sets a new value to the place. Here you can see that SBCL uses
SB-KERNEL:%SETELT
to set the value.The first value is a list of variable names that should be bound to the values returned by the forms in the second value when evaluating the setter/getter forms. The third value is a list of store variables, which should be bound to the new values to be stored by the setter.
With these we can define a simple
MY-SETF
-macro.All we need to do is to bind the variables, and evaluate the setter. Notice that the environment should be passed to
GET-SETF-EXPANSION
. We ignore the fifth value (the getter), since we don't need it.MULTIPLE-VALUE-BIND
is used to bind the store variables, because there may be more than one of them.There are various ways to define your own places. The easiest ways are to use
DEFSETF
or just define a setf-function withDEFUN
. For example: