0

This question is about the Common Lisp setf macro, and how it evaluates its argument forms (and subforms)--namely, only once if they happen to appear more than once. (It is also partly follow-up to an example given in the comments at Using get-setf-expansion.)

;create a list of two hash tables
* (defparameter hts (list (make-hash-table) (make-hash-table)))
HTS
* hts
(#<HASH-TABLE :TEST EQL :COUNT 0 {1007F76CB3}>
 #<HASH-TABLE :TEST EQL :COUNT 0 {1007F77103}>)

;define a function that swaps the position of the two hash tables
* (defun next-ht (hts) 
    (rotatef (first hts) (second hts))
    (second hts))
NEXT-HT

Swapping:

;now do a swap to verify it works
* (next-ht hts)
#<HASH-TABLE :TEST EQL :COUNT 0 {1007F76CB3}>
* hts
(#<HASH-TABLE :TEST EQL :COUNT 0 {1007F77103}>
 #<HASH-TABLE :TEST EQL :COUNT 0 {1007F76CB3}>)

;and swap them back
* (next-ht hts)
#<HASH-TABLE :TEST EQL :COUNT 0 {1007F77103}>
* hts
(#<HASH-TABLE :TEST EQL :COUNT 0 {1007F76CB3}>
 #<HASH-TABLE :TEST EQL :COUNT 0 {1007F77103}>)

Further testing:

;then set different values for a key in each table
* (setf (gethash 0 (first hts)) 11)
11
* (setf (gethash 0 (second hts)) 22)
22
* hts
(#<HASH-TABLE :TEST EQL :COUNT 1 {1007F76CB3}>
 #<HASH-TABLE :TEST EQL :COUNT 1 {1007F77103}>)

;finally execute a setf with a swapping side-effect
* (setf (gethash 0 (next-ht hts)) (1+ (gethash 0 (next-ht hts))))
23

;but it looks like hts has been swapped twice
;back to its original state
* hts
(#<HASH-TABLE :TEST EQL :COUNT 1 {1007F76CB3}>
 #<HASH-TABLE :TEST EQL :COUNT 1 {1007F77103}>)

;also, where did the initial value of 11 go?
* (gethash 0 (first hts))
23
T
* (gethash 0 (second hts))
22
T
*

Can someone clarify what's happening? Also, what is the meaning of a setf expression with a side-effect?

Community
  • 1
  • 1
davypough
  • 1,847
  • 11
  • 21
  • 1
    `SETF` doesn't prevent subforms from being evaluated twice. Modify macros like `INCF` do that. – jkiiski Feb 18 '17 at 19:37
  • @jkiiski This is the distinction I was looking for. I previously was under the impression that `setf` *was* some kind of modify macro, since the place is simply passed on to `get-setf-expansion`. Thanks. – davypough Feb 19 '17 at 19:34

1 Answers1

4

Why not macroexpand the setf form? Here LispWorks:

CL-USER 32 > (pprint (macroexpand '(setf (gethash 0 (next-ht hts))
                                         (1+ (gethash 0 (next-ht hts))))))

(LET* ((#:|key1014| 0)
       (#:|table1015| (NEXT-HT HTS))
       (#:|default1016| NIL)
       (#:|store1017| (1+ (GETHASH 0 (NEXT-HT HTS)))))
  (SYSTEM::%PUTHASH #:|key1014| #:|table1015| #:|store1017|))

What does it do?

  • get the key value
  • get the hash-table , calls NEXT-HT
  • get the default value, not used
  • get the new value , calls NEXT-HT
  • store the new key/value into the hash-table, using some implementation specific way

So clearly NEXT-HT is called twice.

What is the rough (!) conceptual model behind it?

  • setf will examine the first expression.
  • what is it? oh it is gethash, let me set up the setter form for it
  • the setter form then will evaluate the necessary subforms from the first form
  • it will then compute the new value
  • the setter operator will be called with those arguments.

Example:

CL-USER 62 > (setf (gethash (print 0)
                            (print (next-ht hts))
                            (print 1))
                   (print (1+ (print (gethash 0
                                              (print (next-ht hts))
                                              2)))))

0 
#<EQL Hash Table{1} 402000137B> 
1 
#<EQL Hash Table{0} 4020001573> 
2 
3 
3   ; return value
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • Thanks for summarizing the `setf` evaluation model. But what I was trying to better understand (with reference to the Code Review post above) is why acelent explicitly recommended using setf expressions over writing your own modify macros with `get-setf-expansion`. It's because you yourself must be careful to evaluate subforms only once; `get-setf-expansion` does not do this for you. Is this right? It may be marginally "off-topic", but I would like to see a similar summary of the `get-setf-expansion` operation. – davypough Feb 19 '17 at 19:35
  • It's taken me a while to mine the information at the above Code Review post, and I realize now that I didn't really need to ask this question. Most answers are already there. Feel free to delete it, or use it for further educational purposes if you have anything to add. But thanks for the time, effort, and clarifications. – davypough Feb 20 '17 at 21:19