[comp.lang.lisp] Use of backquote & macro question

rcp@perseus.sw.mcc.com (Rob Pettengill) (04/27/89)

In the course of writing the following macro I ran into the limitation
that backquote appears to be valid only when used on an evaluated form
(eg not in the binding clause of a let).  This appears to be
reasonable although I can't find an explicit discussion of where it is
allowed in CLtL.  In any case I was able to write a nice general
purpose macro for temporarily overriding state in a defstruct or CLOS
instance.  However, I was only able to do this by using an explicit
eval at macro expansion time.  Normally the explicit use of eval is
red flag so I am curious to see if anyone can propose a better
solution to this problem:

(defmacro with-context
  ((state-holder state-binding-list) &body body)
  "This macro overrides the state in the STATE-HOLDER with the values in 
   the STATE-BINDING-LIST for the scope of the BODY.  The STATE-HOLDER
   typically a defstruct or CLOS instance although it may be any lisp
   object with  SETFable accessors.  An unwind-protect ensures that the
   original STATE-HOLDER state is restored when the body is exited.  The
   STATE-BINDING-LIST is a list of alternate state accessors and new bindings.
   The STATE-HOLDER and the bindings are evaluated.  The symbols which name
   the stare accessors are not.  All of the accessors must be symbols which
   name valid SETF-able accessors for the given STATE-HOLDER.
   Example:
   <cl> (defstruct bar a b)
   BAR 
   <cl> (setq bar1 (make-bar :a 1 :b 2))
   #s(BAR :A 1 :B 2) 
   <cl> (with-context (bar1 (bar-a 'a bar-b (+ 1 2)))
          (format t \"~%~S\" bar1) bar1)
   #s(BAR :A A :B 3)
   #s(BAR :A 1 :B 2) 
  "
;; (macroexpand-1 '(with-context (bar1 (bar-a 'a bar-b (+ 1 2)))
;; 			    (format t "~%~S" bar1) bar1))
;; returns:
;; (LET ((#:G88 (BAR-B BAR1))
;;       (#:G87 (BAR-A BAR1)))
;;      (SETF (BAR-B BAR1) (+ 1 2))
;;      (SETF (BAR-A BAR1) (QUOTE A))
;;      (UNWIND-PROTECT
;;       (PROGN
;;        (FORMAT T "~%~S" BAR1) BAR1)
;;       (SETF (BAR-B BAR1) #:G88)
;;       (SETF (BAR-A BAR1) #:G87)))
;; 
  (eval
  `(let				; first set up bindings to be useful later
       ,(do ((state-var (car state-binding-list) (car rest)) ;even members
	     (state-binding (cadr state-binding-list) (cadr rest)) ;odd
	     (rest (cddr state-binding-list) (cddr rest))
	     (state-vars ())	;setfable state accessors
	     (state-vals ())	;new temporary bindings
	     (state-temps ())	;temporary storage for previous bindings
	     )
	    ((null state-var) `((state-vars ',state-vars) ;format for let
				(state-vals ',state-vals)
				(state-temps ',state-temps)))
	  (push state-var state-vars) ; gather the lists
	  (push state-binding state-vals)
	  (push (gensym) state-temps)
	  )
     ;; 
     `(let			;save the old state in temp vars
	   ,(mapcar
	    #'(lambda (tmp-var var)
		(list tmp-var (list var ',state-holder)))
	    state-temps state-vars)
	 ;; set the new state in the state-holder
	 ,@(mapcar
	    #'(lambda (var val) (list 'setf (list var ',state-holder) val))
	    state-vars state-vals)
	 ;; run the body with unwind-protect
	 (unwind-protect
	     ,(cons 'progn ',body)
	   ;; restore the original state-holder state
	   ,@(mapcar
	      #'(lambda (var val) (list 'setf (list var ',state-holder) val))
	      state-vars state-temps)
	   )
	 )
    )))

;rob

harrisr@cs.rpi.edu (Richard Harris) (04/28/89)

In article <2277@perseus.sw.mcc.com> rcp@perseus.sw.mcc.com (Rob Pettengill) writes:
 ...
>In any case I was able to write a nice general
>purpose macro for temporarily overriding state in a defstruct or CLOS
>instance.  However, I was only able to do this by using an explicit
>eval at macro expansion time.  Normally the explicit use of eval is
>red flag so I am curious to see if anyone can propose a better
>solution to this problem:
>
>(defmacro with-context
>  ((state-holder state-binding-list) &body body)
>  "This macro overrides the state in the STATE-HOLDER with the values in 
>   the STATE-BINDING-LIST for the scope of the BODY.  The STATE-HOLDER
>   typically a defstruct or CLOS instance although it may be any lisp
>   object with  SETFable accessors.  An unwind-protect ensures that the
>   original STATE-HOLDER state is restored when the body is exited.  The
>   STATE-BINDING-LIST is a list of alternate state accessors and new bindings.
>   The STATE-HOLDER and the bindings are evaluated.  The symbols which name
>   the stare accessors are not.  All of the accessors must be symbols which
>   name valid SETF-able accessors for the given STATE-HOLDER.
>   Example:
>   <cl> (defstruct bar a b)
>   BAR 
>   <cl> (setq bar1 (make-bar :a 1 :b 2))
>   #s(BAR :A 1 :B 2) 
>   <cl> (with-context (bar1 (bar-a 'a bar-b (+ 1 2)))
>          (format t \"~%~S\" bar1) bar1)
>   #s(BAR :A A :B 3)
>   #s(BAR :A 1 :B 2) 
>  "

Here is a definition of with-context that does not use EVAL.
Instead, this definition uses a macro called LETF (defined below).

(defmacro with-context ((state-holder-form state-binding-list) &body body)
  (let ((state-holder (gensym))
	(state-binding-functions nil)
	(state-binding-forms nil))
    (do ((functions nil (cons (car sbl-tail) functions))
	 (forms nil (cons (car sbl-tail) functions))
	 (sbl-tail state-binding-list (cddr sbl-tail)))
	((null sbl-tail)
	 (setq state-binding-functions (nreverse functions))
	 (setq state-binding-forms (nreverse forms))))
    `(let ((,state-holder ,state-holder-form))
       (letf ,(mapcar #'(lambda (function form)
			  `((,function ,state-holder) ,form))
	              state-binding-functions
	              state-binding-forms)
	 ,@body))))

(export '(LETF LETF*))

;The function LETF (defined below) is really a cross between the (Symbolics) functions
;LETF and LET-GLOBALLY, and should really be called LETF-GLOBALLY.

;"letf plaves-and-values body...              Special form
;      Just like let, except that it can bind any storage cells rather than just variables."
;"let-globally ((var value)...) body...       Special form
;      Similar in form to let.  The difference is that let-globally does not bind the
;      variables; instead, it saves the old values and sets the variables, and sets up
;      an unwind-protect to set them back."
;      The difference between let and let-globally is important (only)
;      in a multiple-process Lisp system.

(defmacro letf (bindings &body forms)
  (let ((tvars nil)
	(tvals nil)
	(store-vars nil)
	(store-forms nil)
	(access-forms nil)
	(value-forms nil)
	(save-vars nil))
    (dolist (binding bindings)
      (let ((setf-form (if (atom binding) binding (car binding)))
	    (value-form (if (atom binding) nil (cadr binding))))
	(multiple-value-bind (vars vals stores store-form access-form)
	    (get-setf-method setf-form)
	  (setq tvars (nconc tvars vars))
	  (setq tvals (nconc tvals vals))
	  (setq store-vars (nconc store-vars stores))
	  (setq store-forms (nconc store-forms (list store-form)))
	  (setq access-forms (nconc access-forms (list access-form)))
	  (setq value-forms (nconc value-forms (list value-form)))
	  (setq save-vars (nconc save-vars (list (gensym)))))))
    `(let ,(mapcar #'list tvars tvals)
       (let ,(mapcar #'list save-vars access-forms)
	 (unwind-protect
	      (progn
		(let ,(mapcar #'list store-vars value-forms)
		  ,@store-forms)
		,@forms)
	   (let ,(mapcar #'list store-vars save-vars)
	     ,@store-forms))))))

(defmacro letf* (bindings &body forms)
  (if (null (cdr bindings))
      `(letf ,bindings
	  ,@forms)
      `(letf (,(car bindings))
	  (letf* ,(cdr bindings)
	     ,@forms))))

barmar@think.COM (Barry Margolin) (04/28/89)

You are correct that backquote only works in an evaluation context.
On p.349 of CltL, it says "Note that the [resulting] form is
equivalent only in the sense that when it is evaluated it will
calculate the correct result."  Backquote expands into a form that
constructs the specified data structure, but it has to be invoked
somehow.  If it's used in a non-evaluation context, it will simply be
left there.

For instance, a possible expansion of `(a ,b c) is (list (quote a) b
(quote c)).  If this were used in a let-binding, e.g.

	(let `(a ,b c)
	  ...)

the result will be

	(let (list
	      (quote a)
	      b)
	  ...)

which will bind the variables LIST and B to NIL, and bind the variable
QUOTE to the value of A.

Note, however, that you can frequently solve this problem by
restructuring the expression.  What was probably mean above was
something like

	`(let (a ,b c) ...)

which uses the value (at macro-expansion time) of B as the
let-binding.

Barry Margolin
Thinking Machines Corp.

barmar@think.com
{uunet,harvard}!think!barmar