Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Functions -> setf Functions needs better example #544

Open
bpseudopod opened this issue Jun 13, 2024 · 3 comments
Open

Functions -> setf Functions needs better example #544

bpseudopod opened this issue Jun 13, 2024 · 3 comments

Comments

@bpseudopod
Copy link
Contributor

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.

@vindarel
Copy link
Contributor

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

@cgay
Copy link
Contributor

cgay commented Jun 13, 2024

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

@vindarel
Copy link
Contributor

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 added a commit that referenced this issue Jun 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants