CLHS example with long form of DEFSETF doesn't work

Bug #1452947 reported by Douglas Katzman
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
SBCL
New
Undecided
Douglas Katzman

Bug Description

The example involving keyword args tries (with success) to make things as maximally confusing as possible, because the keyword argument indicators happen not to be symbols in the "KEYWORD" package:

(defsetf xy (&key ((x x) 0) ((y y) 0)) (store)
   `(set-xy ,store 'x ,x 'y ,y)) => XY

CCL gets pretty close to being right, which is to say, the result of GET-SETF-EXPANSION is as indicated by the example, but the example is wrong!

The fifth value of (get-setf-expansion '(xy a b)) is shown as being (xy #:t0 #:t1) which doesn't work if you try something like:
 (push 'foo (xy 'y 0 'x 1))

I guess nobody needs that, because every other Lisp implementation I tried crashed in GET-SETF-EXPANSION.
Nonetheless, I think we can get this example to work.

Douglas Katzman (dougk)
Changed in sbcl:
assignee: nobody → Douglas Katzman (dougk)
Revision history for this message
Douglas Katzman (dougk) wrote :
Download full text (3.4 KiB)

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 ...

Read more...

To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.