Functions -> setf Functions needs better example
The current example did not help me approach custom places. Issues include:
hellois not an "accessor" of anything, therefore(setf hello)doesn't make much sense to begin with.(setf hello)ignores the argument convention at the beginning of the section, and so doesn't show how a lambda list changes between a function and itssetfcounterpart.(setf hello)has a number of unrelated side effects, cluttering the code and making its purpose unclear.(setf hello)ignores the convention that asetfform should return the place's new value.
My experience: Today I wanted to write a setf function--the Hyperspec isn't very clear on the matter, so I turned to the Cookbook, and found the matter even less clear here. (I ended up using another resource.) A better example would consist of an accessor function and setf counterpart with a more obviously symmetrical relationship that follows setf conventions.
hello,
WDYT of this?
setf functions
A function name can also be a list of two symbols with setf as the
first one, and where the first argument is the new value:
(defun (setf <name>) (new-value <other arguments>)
body)
This mechanism is often used for CLOS methods.
Let's work towards an example. Let's say we manipulate a hash-table that represents a square. We store the square width in this hash-table:
(defparameter *square* (make-hash-table))
(setf (gethash :width *square*) 21)
During our program life cycle, we can change the square width, with setf as we did above.
We define a function to compute a square area. We don't store it in the hash-table as it is redundant with the dimension.
(defun area (square)
(expt (gethash :width square) 2))
Now, our programming needs lead to the situation where it would be very handy to change the area of the square… and have this reflected on the square's dimensions. It can ergonomic for your program's application interface to define a setf-function, like this:
(defun (setf area) (new-area square)
(let ((width (sqrt new-area)))
(setf (gethash :width square) width)))
And now you can do:
(setf (area *SQUARE*) 100)
;; => 10.0
and check your square (describe, inspect…), the new width is correc.t
The treatment of setf in CLtL2 is pretty good and could be used as inspiration. (Even if it's motivational examples are based on the fact that CL has terrible function naming to begin with. If the names were consistently named set-foo instead of things like rplaca and set, they would be easy to remember. So maybe nevermind this point. /grin)
I think it's pretty important to talk explicitly about places, as the OP mentioned. CLtL2 calls them "generalized variables", which is too many syllables IMO. Perhaps a basic structure like this:
- CL has a concept of places.
- Here are some examples using built-in place setters.
- Here's how you can define your own place setters.
- Here are some links to read more about it.
Other random thoughts:
- This section also talks about setf and could link to the more general discussion of
setfthat (I think) we're discussing here. - maybe it's worth mentioning
(setf (values ...) ...)andpsetfor just linking to Kinds of Places - There's a pattern that might be worth mentioning: if you are holding two bits of state, let's say
state1andstate2, andstate2is (expensively) derived fromstate1, then rather than giving users direct access tostate1you might give them(setf (state1 x) ...)instead, and internally make surestate2is recalculated. Or maybe in a UI, if a place updated it also needs to trigger redisplay....
ACK, thanks.
I think I covered the last point with state1 and state2, being width and area (which is, well, not a state stored in the hash-table).