cl-cookbook icon indicating copy to clipboard operation
cl-cookbook copied to clipboard

Functions -> setf Functions needs better example

Open bpseudopod opened this issue 1 year ago • 3 comments

The current example did not help me approach custom places. Issues include:

  • hello is 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 its setf counterpart.
  • (setf hello) has a number of unrelated side effects, cluttering the code and making its purpose unclear.
  • (setf hello) ignores the convention that a setf form 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.

bpseudopod avatar Jun 13 '24 06:06 bpseudopod

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

vindarel avatar Jun 13 '24 13:06 vindarel

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 setf that (I think) we're discussing here.
  • maybe it's worth mentioning (setf (values ...) ...) and psetf or 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 state1 and state2, and state2 is (expensively) derived from state1, then rather than giving users direct access to state1 you might give them (setf (state1 x) ...) instead, and internally make sure state2 is recalculated. Or maybe in a UI, if a place updated it also needs to trigger redisplay....

cgay avatar Jun 13 '24 19:06 cgay

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).

vindarel avatar Jun 13 '24 21:06 vindarel