gyro@kestrel.edu (Scott Layson Burson) (05/22/91)
I have just come to the conclusion, after trying for several hours, that it is not possible to write a SETF method for LET using DEFINE-SETF-METHOD. In case it's not obvious what that means, here's one not-quite-right way to do it: (define-setf-method let (clauses &rest body) (let ((storevar (gensym))) (values '() '() (list storevar) `(let ,clauses ,@(butlast body) (setf ,(car (last body)) ,storevar) (car (last body)))))) So, roughly speaking, (setf (let ((x ...)) (car x)) 'foo) turns into (let ((x ...)) (setf (car x) 'foo)). The problem with the definition above is that it potentially evaluates the subforms of the last form in the body more than once; so (incf (let ((x ...)) (car (pop x))) 3) turns into (effectively) (let ((x ...)) (setf (car (pop x)) (+ 3 (car (pop x))))) I can fix this problem, but then I get a version that doesn't correctly handle DECLARE forms immediately inside the LET being SETFed. The point is not that it can't be done -- as far as I know, it is not hard to specify what the expansion of SETF of LET should be -- but that it can't be done with the DEFINE-SETF-METHOD interface. Why would anyone want this to work? It's not unusual for macros to expand into LET forms. It's true, I could write SETF methods for each such macro individually, but I don't see why I should have to. So I'm surprised that CLtL2 doesn't specify (at least not that I've been able to find) that SETF of LET should work. Is this perhaps simply an oversight, that should be brought to the attention of X3J13? -- Scott Gyro@Reasoning.COM
barmar@think.com (Barry Margolin) (05/23/91)
In article <1991May22.072355.22077@kestrel.edu> gyro@kestrel.edu (Scott Layson Burson) writes: >I have just come to the conclusion, after trying for several hours, >that it is not possible to write a SETF method for LET using >DEFINE-SETF-METHOD. You might want to check with the Master Macrologist, Alan Bawden. If it can be done, he can probably figure out how to do it. If it can't, he can probably prove it. > (define-setf-method let (clauses &rest body) > (let ((storevar (gensym))) > (values '() '() (list storevar) > `(let ,clauses > ,@(butlast body) > (setf ,(car (last body)) ,storevar) > (car (last body)))))) > >So, roughly speaking, (setf (let ((x ...)) (car x)) 'foo) turns into >(let ((x ...)) (setf (car x) 'foo)). > >The problem with the definition above is that it potentially evaluates >the subforms of the last form in the body more than once; so Could the problem be that you're expanding into another SETF, rather than using GET-SETF-METHOD-MULTIPLE-VALUE on (car (last body))? If the last form in the body needs special attention like this, its SETF expansion should take care of it. >So I'm surprised that CLtL2 doesn't specify (at least not that I've >been able to find) that SETF of LET should work. Is this perhaps >simply an oversight, that should be brought to the attention of X3J13? I think it's just an oversight by the original SETF designers in MacLisp, which the CL SETF is basically a clone of. You can bring it to our attention, but I don't think anything will come of it in the near future, as it's too late to add something significant like this to the language. -- Barry Margolin, Thinking Machines Corp. barmar@think.com {uunet,harvard}!think!barmar
moore%defmacro.utah.edu@cs.utah.edu (Tim Moore) (05/23/91)
In article <1991May22.180507.4914@Think.COM> barmar@think.com writes: >In article <1991May22.072355.22077@kestrel.edu> gyro@kestrel.edu (Scott Layson Burson) writes: >>I have just come to the conclusion, after trying for several hours, >>that it is not possible to write a SETF method for LET using >>DEFINE-SETF-METHOD. > ... >> (define-setf-method let (clauses &rest body) >> (let ((storevar (gensym))) >> (values '() '() (list storevar) >> `(let ,clauses >> ,@(butlast body) >> (setf ,(car (last body)) ,storevar) >> (car (last body)))))) >> >>So, roughly speaking, (setf (let ((x ...)) (car x)) 'foo) turns into >>(let ((x ...)) (setf (car x) 'foo)). >> >>The problem with the definition above is that it potentially evaluates >>the subforms of the last form in the body more than once; so > >Could the problem be that you're expanding into another SETF, rather than >using GET-SETF-METHOD-MULTIPLE-VALUE on (car (last body))? If the last >form in the body needs special attention like this, its SETF expansion >should take care of it. I fooled around a bit with this problem this morning. The bindings of the let probably need to be bound around the accessor form too, so you want to move the let bindings into the vars and vals returned by define-setf-method. BUT define-setf-method has to return gensyms (or gentemps) for its bindings. So you need to augment the environment passed to the inner GET-SETF-METHOD-MULTIPLE-VALUE with symbol-macro bindings that substitute references to the variables of the LET with refs to temporaries. For good measure, you should add new declaration info (with reference to the temporaries) to the environment too. Then wrap LOCALLY around the store and accessor forms, and you're set. Unfortunately, there won't be a portable way to do this in ANSI Common Lisp because the environment functions from Chapter 8 of CLtL2 were booted at the last meeting. You basically need to do a codewalk to do this right. > >>So I'm surprised that CLtL2 doesn't specify (at least not that I've >>been able to find) that SETF of LET should work. Is this perhaps >>simply an oversight, that should be brought to the attention of X3J13? > >I think it's just an oversight by the original SETF designers in MacLisp, >which the CL SETF is basically a clone of. You can bring it to our >attention, but I don't think anything will come of it in the near future, >as it's too late to add something significant like this to the language. I can't see that SETF of LET would really be that useful. Unless you make restrict the last form the LET to be a generalized location, which seems very restrictive, you would have to write setf methods for every special form in the language to make this work in the general case. -- Tim Moore moore@cs.utah.edu {bellcore,hplabs}!utah-cs!moore "Ah, youth. Ah, statute of limitations." -John Waters
john@thelonius.mitre.org (John D. Burger) (05/24/91)
Here's one implementation, at the end of this message. As Tim Moore suggests, you'd ideally do it with symbol macros and augmentating environments, but I think you can do the same thing by establishing the appropriate environment around the relevant subforms with a number of LETs. One complication is that the temporary variables returned from a SETF method are bound in a LET*, but a LET's bindings have to be done in parallel. This makes the SETF method for LET* simpler, so I've included that first, as a build-up to the LET method. Another thing common to both is that the body of the LET (minus the last subform) has to be evaluated in the context of the LET bindings, but before the access and store forms for the last subform are evaluated. I've done that here with a bogus variable binding. I have to agree with Tim in that I wouldn't find this to be very useful. Most macro definitions I write require rather idiosyncratic SETF methods. Anyway, this definition gives this example: (setf (let ((x (foo 1 2)) (y (bar 3 4))) (do-stuff x y) (car x)) z) the following expansion, modulo some renamed variables: (let* ((temp-x nil) (temp-y nil) (bogus (progn (psetf temp-x (foo 1 2) temp-y (bar 3 4)) (let ((x temp-x) (y temp-y)) x y (do-stuff x y)))) (temp-cons (let ((x temp-x) (y temp-y)) x y x)) (new-car z)) (let ((x temp-x) (y temp-y)) x y (locally (declare (ignore bogus)) (rplaca temp-cons new-car) new-car))) ------------------------- Lisp Code Follows ------------------------- (define-setf-method let* (clauses &rest body) (let ((setf-subform (first (last body))) (other-subforms (butlast body)) (let-vars '()) (let-forms '()) (temp-let-vars '()) (bogus-var (make-symbol "BOGUS"))) ;; Process LET* clauses (dolist (clause clauses) (let ((let-var nil) (let-form nil) (temp-let-var (gensym))) (cond ((listp clause) (setf let-var (first clause) let-form (second clause))) (t (setf let-var clause))) (push let-var let-vars) (push let-form let-forms) (push temp-let-var temp-let-vars))) (setf let-vars (nreverse let-vars) let-forms (nreverse let-forms) temp-let-vars (nreverse temp-let-vars)) (flet ((wrap-it (body-forms) "Establish the right variable bindings around some subforms" `(let ,(mapcar #'(lambda (let-var temp-let-var) `(,let-var ,temp-let-var)) let-vars temp-let-vars) ,@let-vars ; Make sure each var gets used . ,body-forms))) ;; Get SETF method for subform to be SETFed (multiple-value-bind (subform-temp-vars subform-temp-forms subform-store-vars subform-store-form subform-access-form) (get-setf-method setf-subform) ;; Do it (values `(,@temp-let-vars ,bogus-var . ,subform-temp-vars) `(,@let-forms ,(wrap-it other-subforms) . ,(mapcar #'(lambda (form) (wrap-it (list form))) subform-temp-forms)) subform-store-vars (wrap-it `((locally (declare (ignore ,bogus-var)) ,subform-store-form))) (wrap-it `((locally (declare (ignore ,bogus-var)) ,subform-access-form)))))))) (define-setf-method let (clauses &rest body) (let ((setf-subform (first (last body))) (other-subforms (butlast body)) (let-vars '()) (temp-let-vars '()) (psetf-args '()) (list-o-nils (make-list (length clauses) :initial-element nil)) (bogus-var (make-symbol "BOGUS"))) ;; Process LET clauses (dolist (clause clauses) (let ((let-var nil) (let-form nil) (temp-let-var (gensym "TEMP"))) (cond ((listp clause) (setf let-var (first clause) let-form (second clause))) (t (setf let-var clause))) (push let-var let-vars) (push temp-let-var temp-let-vars) ;; We're going to PSETF each temporary LET var ;; to the appropriate LET form (push temp-let-var psetf-args) (push let-form psetf-args))) (setf let-vars (nreverse let-vars) temp-let-vars (nreverse temp-let-vars) psetf-args (nreverse psetf-args)) (flet ((wrap-it (body-forms) "Establish the right variable bindings around some subforms" `(let ,(mapcar #'(lambda (let-var temp-let-var) `(,let-var ,temp-let-var)) let-vars temp-let-vars) ,@let-vars ; Make sure each var gets used . ,body-forms))) ;; Get SETF method for subform to be SETFed (multiple-value-bind (subform-temp-vars subform-temp-forms subform-store-vars subform-store-form subform-access-form) (get-setf-method setf-subform) ;; Do it (values `(,@temp-let-vars ,bogus-var . ,subform-temp-vars) `(,@list-o-nils (progn (psetf . ,psetf-args) ,(wrap-it other-subforms)) . ,(mapcar #'(lambda (form) (wrap-it (list form))) subform-temp-forms)) subform-store-vars (wrap-it `((locally (declare (ignore ,bogus-var)) ,subform-store-form))) (wrap-it `((locally (declare (ignore ,bogus-var)) ,subform-access-form)))))))) -- John Burger john@mitre.org "You ever think about .signature files? I mean, do we really need them?" - alt.andy.rooney
john@thelonius.mitre.org (John D. Burger) (05/24/91)
Concerning the implementation I posted, I just realized that it doesn't do the right thing with respect to declarations in the body of the LET. Those need to be parsed out of the body, and then the internal WRAP-IT function needs to include them. The code also ought to check for IGNOREs in these declarations, just in case some idiot binds a variable and then immediately ignores it. All of this results in an even more odious piece of code than already exists. -- John Burger john@mitre.org "You ever think about .signature files? I mean, do we really need them?" - alt.andy.rooney