[comp.lang.lisp] SETF of LET

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