Re: [PATCH] Fix broken defsetf for places with keywords.
Bruno Haible <[email protected]> Sat, 29 Jun 2019 17:18:43 +0200
| Newsgroups | gmane.lisp.clisp.devel |
|---|---|
| Message-ID | <2128990.biAILaZPxs@omega> |
Sam Steingold wrote:
> Sorry about not replying earlier - I thought that you were actually
> planning to work more on your patch.
Very good point. My initial reaction to Kaz' patch in
<https://sourceforge.net/p/clisp/mailman/message/36601264/>
was "who needs the generic case, the frequent case is that the keywords
are known at compile time". The only thing I did not like about it was
the (not (constantp ev-argform)) - the error checking of the keywords
should be done by DEFSETF, not in GET-SETF-EXPANSION.
But you are absolutely right: When writing compilers (and macroexpansion
code), one should strive for maximum generality, and the optimizations
of common cases are only special cases.
So, looking at
http://www.ai.mit.edu/projects/iiip/doc/CommonLISP/HyperSpec/Body/sec_5-1-1-2.html
http://www.ai.mit.edu/projects/iiip/doc/CommonLISP/HyperSpec/Body/fun_get-setf-expansion.html
http://www.ai.mit.edu/projects/iiip/doc/CommonLISP/HyperSpec/Body/mac_defsetf.html
there is even an example regarding computed keywords:
(defsetf xy (&key ((x x) 0) ((y y) 0)) (store)
`(set-xy ,store 'x ,x 'y ,y)) => XY
(get-setf-expansion '(xy a b))
=> (#:t0 #:t1),
(a b),
(#:store),
((lambda (&key ((x #:x)) ((y #:y)))
(set-xy #:store 'x #:x 'y #:y))
#:t0 #:t1),
(xy #:t0 #:t1)
This example gives an important clue: How to use a LAMBDA for keyword matching.
But this example is wrong. It ignores the keyword init forms. The correct
result is:
(get-setf-expansion '(xy a b))
=> (#:t0 #:t1),
(a b),
(#:store),
((lambda (&key ((x #:x)) ((y #:y)))
(set-xy #:store 'x #:x 'y #:y))
#:t0 #:t1 'x 0 'y 0),
(xy #:t0 #:t1 'x 0 'y 0)
or
=> (#:t0 #:t1),
(a b),
(#:store),
((lambda (&key ((x #:x) 0) ((y #:y) 0))
(set-xy #:store 'x #:x 'y #:y))
#:t0 #:t1),
(xy #:t0 #:t1 'x 0 'y 0)
Things get more hairy when we think of keyword init forms that are
not constant forms. In which environment do they get evaluated?
Example:
(defun zero () 0)
(flet ((default-form-for-y () '(zero)))
(defsetf xy (&key ((x x) '(zero)) ((y y) (default-form-for-y))) (store)
`(set-xy ,store 'x ,x 'y ,y))) => XY
There are two evaluations:
1. At macro expansion time, i.e. during get-setf-expansion,
(default-form-for-y) gets evaluated, in the lexical environment of the DEFSETF form.
=> (ZERO).
2. At runtime, (ZERO) gets evaluated. => 0.
So, DEFSETF needs to store a closure that will evaluate
(default-form-for-y).
The other thing is: How should the desired result of GET-SETF-EXPANSION
look like? Let's take an example with keywords arguments that are not in
the keyword package, non-constant keyword init forms, and with a required
argument as well.
> (defsetf xy (r &key ((x x) '(zero)) ((y y) '(zero))) (store) `(set-xy ,store ,r 'x ,x 'y ,y))
XY
The simple case, when all keywords are specified as constants, is:
> (get-setf-expansion '(xy 3 'x 0 'y 0))
(#:G1 #:G2 #:G3) ;
(3 0 0) ;
(#:G4) ;
(SET-XY #:G4 #:G1 'X #:G2 'Y #:G3) ;
(XY #:G1 'X #:G2 'Y #:G3)
The general case, when all keyword arguments are non-constant, requires
the LAMBDA trick, and the mechanism for using the keyword init form
shown above:
> (get-setf-expansion '(xy 3 a 0))
(#:G1 #:G2 #:G3 #:G4 #:G5) ;
(3 A 0 (ZERO) (ZERO)) ;
(#:G6) ;
((LAMBDA (&KEY ((X #:G7)) ((Y #:G8))) (SET-XY #:G6 #:G1 'X #:G7 'Y #:G8)) #:G2 #:G3 'X #:G4 'Y #:G5) ;
(XY #:G1 #:G2 #:G3 'X #:G4 'Y #:G5)
In the mixed case, when some of the keyword arguments are constant,
the corresponding expansion can be optimized to omit the init forms
that are not needed:
> (get-setf-expansion '(xy 3 a 0 'y 0))
(#:G1 #:G2 #:G3 #:G4 #:G5) ;
(3 A 0 0 (ZERO)) ;
(#:G6) ;
((LAMBDA (&KEY ((X #:G7)) ((Y #:G8))) (SET-XY #:G6 #:G1 'X #:G7 'Y #:G8)) #:G2 #:G3 'Y #:G4 'X #:G5) ;
(XY #:G1 #:G2 #:G3 'Y #:G4 'X #:G5)
This was the hard part. Actually implementing it was easy, then.
Done and pushed (including your test case).
Bruno
_______________________________________________
clisp-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/clisp-devel