Cooking with Lisp

Another blog about Lisp, the world's greatest programming language.

Thursday, February 02, 2006

How to Call a Setf Method Directly

Yesterday I wanted to wrap a library's setf method in another setf method that I was writing, but I wasn't sure how to directly call the other library's setf method.

Say I have the following:
(defclass foo ()
((foo-slot-1 :accessor foo-slot-1)))

(defparameter *a-foo* (make-instance 'foo))
Then if I macroexpand the call to (using SBCL):
(macroexpand-1 '(setf (foo-slot-1 *a-foo*) 99))
I get the following (with some cleaning up of the gensyms):
(let* ((#:temp1 *a-foo*))
(multiple-value-bind (#:temp0)
99
(funcall #'(setf foo-slot-1) #:temp0 #:temp1)))
;; i.e., (funcall #'(setf foo-slot-1) 99 *a-foo*)
That function argument #'(setf foo-slot-1) looks pretty strange, so I tried finding out what is going on here.

After a lot of research, here's what I found.

From the HyperSpec, funcall has the following syntax:
funcall function &rest args => result*
function---a function designator.
The glossary entry for 'function designator' isn't too helpful
function designator n. a designator for a function; that is, an object that denotes a function and that is one of: a symbol (denoting the function named by that symbol in the global environment), or a function (denoting itself). The consequences are undefined if a symbol is used as a function designator but it does not have a global definition as a function, or it has a global definition as a macro or a special form. See also extended function designator.
Since #'(setf a) is clearly not a symbol, it must be a function, but that's certainly a weird way to designate a function.

The entry for "extended function designator" isn't by itself any more useful, but provides a small clue:
extended function designator n. a designator for a function; that is, an object that denotes a function and that is one of: a function name (denoting the function it names in the global environment), or a function (denoting itself). The consequences are undefined if a function name is used as an extended function designator but it does not have a global definition as a function, or if it is a symbol that has a global definition as a macro or a special form. See also function designator.
The key here is "function name":
function name n. 1. (in an environment) A symbol or a list (setf symbol) that is the name of a function in that environment. 2. A symbol or a list (setf symbol).
Additionally, there's also these entries:
setf function n. a function whose name is (setf symbol).

setf function name n. (of a symbol S) the list (setf S).
Aha! So the only two ways to specify a function name is with a symbol (the case we're all familiar with) or the list (setf symbol).

So, we're pretty close. The list (setf foo-slot-1) is a setf function name. You'll note that funcall takes a function designator and not an extended function designator, that is, it takes functions, not function names. Of course, the way to get from function names to functions is to use the function "function" or the better know reader macro #'. Thus, we finally have #'(setf foo-slot-1), which is equivilent to (function (setf foo-slot-1)) (no quote needed for (setf foo-slot-1) because function is a special operator. So, if you type #'(setf foo-slot-1), you'll get back something like #.

The final oddity is the ordering of the arguments, it appears to be

(funcall #(setf symbol) newvalue oldvalue)

This is explained in the sections explaining how setf expansion works:
5.1.2.9 Other Compound Forms as Places

For any other compound form for which the operator is a symbol f, the setf form expands into a call to the function named (setf f). The first argument in the newly constructed function form is newvalue and the remaining arguments are the remaining elements of place. This expansion occurs regardless of whether f or (setf f) is defined as a function locally, globally, or not at all. For example,

(setf (f arg1 arg2 ...) new-value)

expands into a form with the same effect and value as
(let ((#:temp-1 arg1)          ;force correct order of evaluation
(#:temp-2 arg2)
...
(#:temp-0 new-value))
(funcall (function (setf f)) #:temp-0 #:temp-1 #:temp-2...))
A function named (setf f) must return its first argument as its only value in order to preserve the semantics of setf.
Putting the new value first allows everything following to look just like the call to the accessing function, even if it has lots of additional lambda keyword arguments, for example

(setf (mumble object :keyword1 :keyword2) newvalue)

being transformed into something like

(funcall #'(setf mumble) newvalue :keyword1 :keyword2)

Anyway, I was able to successfully call the other library's setf method by following the above example.

My final thoughts, I have a huge amount of respect for the people able to read between the lines of the spec and be able to correctly implement all of the nooks and crannies of Common Lisp.