Comment 1 for bug 1452947

Revision history for this message
Douglas Katzman (dougk) wrote :

This bug touches a dark unexplored corner of the spec. With the exception of the 1 Lisp tested that run the example (CCL), our behavior accords with all others tested; and CCL fails in cases that work in all other implementations under test.
The discrepancy manifests itself in the ambiguity of these two sentences:
- "The long form defsetf resembles defmacro"
   This implies that the expander receives macro-like arguments, as reinforced by backquote-comma in the examples.
- "During the evaluation of the forms, the variables in the lambda-list and the store-variables are bound to *names* of temporary variables ... bound by the expansion of setf to the values of those subforms."
  This implies that each formal variable is bound to a name, where "name" means symbol, and nothing but a symbol.

Consider a &REST arg- does it bind to a name or a list of names? The majority opinion is the the latter, notwithstanding that a list
isn't "a" name, strictly speaking. But the following case works in the majority of CL implementations, as well as OpenLisp which isn't a CL but does support both DEFSETF and &REST args:
* (defsetf foo (first &rest r) (v) `(set-foo ,first ,v ,@r))
* (macroexpand-1 '(setf (foo a b c d) (compute)))
-> (LET* ((#:FIRST A) (#:B536 B) (#:C537 C) (#:D538 D) (#:NEW1 (COMPUTE)))
     (SET-FOO #:FIRST #:NEW1 #:B536 #:C537 #:D538))

The minority opinion is that R actually receives a name (a symbol), not a list, and instead of writing ",@R" you have to write ",R", and you have to APPLY #'SET-FOO. Using ",@R" gets you: (SET-FOO #:FIRST #:G51 . #:R) and using ",R" without APPLY makes SET-FOO receive a list of objects in its last arg, which is presumably *not* a &REST arg.

Everything falls apart from here on due to this difference of opinion. Another usage that disagrees:

* (defsetf baz (a &optional (b nil b-sup-p)) (v)
    `(set-bar ,v ,a ,@(if b-sup-p (list b))))
* (macroexpand-1 '(setf (baz w) (mum))) ; SBCL
    (LET* ((#:A W) (#:NEW1 (MUM))) (SET-BAR #:NEW1 #:A))
* (macroexpand-1 '(setf (baz w) (mum))) ; CCL
 (LET* ((#:G24 W))
   (DECLARE (IGNORABLE #:G24))
   (MULTIPLE-VALUE-BIND (#:G23) (MUM)
    ((LAMBDA (#:A &OPTIONAL (#:B NIL #:B-SUP-P)) (SET-BAR #:G23 #:A #:B)) #:G24)))

In CCL, SET-BAR receives #:B as NIL and can not detect that it was supposed to have received only 1 arg. It could be claimed that these are bugs in that implementation, but I don't think so - it reflects the impossibility of correctly reverse-engineering a random form into a lambda that you can call to "late bind" arguments, on the theory that only a functional lambda can decide how arguments should have been bound. And this is precisely the problem in the keyword parsing example.
If a macro definition includes keyword arguments, but consumers of the macro want to be able to specify non-literal values for those, you're up a creek. You can't have it both ways, and I don't see why the example purports to show that you can.

Incidentally, X3J13 overlooked the obvious elegant solution here: long-form defsetf should not compute a new form dynamically, it should define a single invariant new expression (lambda (<arg> ...) <do-stuff>) that, when placed into the head of the access form in lieu of the access function, receives all the new-value(s) followed by arguments to the access form. This fits with the idea that DEFSETF just substitutes the setter function "name" for the getter name. The macro-like usage is an inferior solution.