random-state.net

Nikodemus Siivola

<< next | top | previous >>

When not to use SETF-functions #
hacking, August 8th 2007

SETF-functions are really nice, but there is one downside to them:

(defvar *foo* (make-array 32))

(defun (setf foo) (value index symbol)
  (setf (getf (svref *foo* index) symbol) value))

(macroexpand '(setf (foo (get-index) (get-symbol)) (get-value)))

=> (LET* ((#:G2017 (GET-INDEX)) 
          (#:G2016 (GET-SYMBOL)))
     (MULTIPLE-VALUE-BIND (#:G2015) (GET-VALUE)
       (FUNCALL #'(SETF FOO) #:G2015 #:G2017 #:G2016)))

A macroexpansion of this sort is necessary to preserve the evaluation order and still pass the result of GET-VALUE as the first argument.

In most cases there is nothing at all wrong with this, but imagine a SETF-function that you want to write a compiler macro for. Compiler macros are great when you can optimize calls that have arguments of a certain form — but in this case the compiler macro doesn't have anything useful to work on, because by the time it sees the call it has only a bunch of gensyms to play with!

While the SETF expander could try to be clever and figure out when it would be safe to just reorder the arguments, I don't recommend relying on that — there are always going to be interesting calls to optimize where the reordering is not going to be safe. Instead, I suggest you do something like this:

(defun set-foo (index symbol value)
  (setf (getf (svref *foo* index) symbol) value))

(defsetf foo set-foo)

(macroexpand '(setf (foo (get-index) (get-symbol)) (get-value)))

=> (SET-FOO (GET-INDEX) (GET-SYMBOL) (GET-VALUE))

Now you can write a compiler-macro for SET-FOO, and it will always see the source forms it needs to see.