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:
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.
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) VALUE-BIND (#:G23) (MUM)
`(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-
((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.