[comp.lang.lisp] Help with Macros longish

bevan@cs.man.ac.uk (Stephen J Bevan) (09/21/90)

I've recently been reading a book on Scheme (The Scheme Programming
Language - R. Kent Dybvig) and in it, it uses a function
`record-case'.  This is similar to `case' except that it does
destructuring.  So for example I could define the following function
which given some expressions as lists, evaluates them.

(defun eval-expr (x)
  (record-case x
    (add (x y) (+ x y))
    (sub (x y) (- x y))
    (mul (x y) (* x y))
    (div (x y) (/ x y))
    )
  )

>(foo '(add 3 4))
7
>(foo '(mul 4 5))
20

This notation is particularly nice if you are writing simple
evaluators for languages.

Here's the problem.  I've tried to implement this in Lisp myself, but
to no avail.  I've RTFM on macros* but I can't seem to get the
arguments to evaluate at the correct time.  So I'm hoping some kind
soul will show me how this is done.  If you are a real masochist I can
even send you the code I've written so far (all 53 lines of it!), so
you can point out what is wrong with it.

Yours a struggling Lisper,

Stephen	J. Bevan		bevan@cs.man.ac.uk


* if fact I've got all of the following in front of me
    Common Lisp, Steele et al.
    Common Lisp - A Tutorial, Wendy L. Milner
    Lisp, Winston & Horn
  but I'm still can't get the parms. to evaluate when I want them to.

moore%cdr.utah.edu@cs.utah.edu (Tim Moore) (09/22/90)

In article <BEVAN.90Sep21154633@panda.cs.man.ac.uk> bevan@cs.man.ac.uk (Stephen J Bevan) writes:
>I've recently been reading a book on Scheme (The Scheme Programming
>Language - R. Kent Dybvig) and in it, it uses a function
>`record-case'.  This is similar to `case' except that it does
>destructuring.  So for example I could define the following function
>which given some expressions as lists, evaluates them.
>
>(defun eval-expr (x)
>  (record-case x
>    (add (x y) (+ x y))
>    (sub (x y) (- x y))
>    (mul (x y) (* x y))
>    (div (x y) (/ x y))
>    )
>  )
>
>>(foo '(add 3 4))
>7
>>(foo '(mul 4 5))
>20
...
>Here's the problem.  I've tried to implement this in Lisp myself, but
>to no avail.  I've RTFM on macros* but I can't seem to get the
>arguments to evaluate at the correct time.  So I'm hoping some kind
>soul will show me how this is done.  If you are a real masochist I can
>even send you the code I've written so far (all 53 lines of it!), so
>you can point out what is wrong with it.

There are a couple of ways you can do this, depending on what kind of
destructuring you want and how close your Lisp is to ANSI Common Lisp.
I'm not sure of the intended syntax and semantics of record-case, but
I'll assume that that case-like dispatching is done on x using the car
of each clause, and that the cadr of each clause specifies
destructuring for the cdr of x.

If you're after simple destructuring like that provided by lambda,
then this kind of approach would work:

(defmacro record-case (expr &body body)
  (let* ((expr-temp (gensym))
	 (expr-cdr-temp (gensym))
	 (new-body (mapcar #'(lambda (clause)
			       `(,(car clause)
				 (apply #'(lambda ,(cadr clause)
					    ,@(cddr clause))
				        ,expr-cdr-temp)))
		      body)))
    `(let* ((,expr-temp ,expr)
	    (,expr-cdr-temp (cdr ,expr-temp)))
       (case (car ,expr-temp)
	 ,@new-body))))

Using this definition,
(record-case x
    (add (x y) (+ x y))
    (sub (x y) (- x y))
    (mul (x y) (* x y))
    (div (x y) (/ x y)))
Expands to:
(LET* ((#:G32 X) (#:G33 (CDR #:G32)))
  (CASE (CAR #:G32)
    (ADD (APPLY #'(LAMBDA (X Y) (+ X Y)) #:G33))
    (SUB (APPLY #'(LAMBDA (X Y) (- X Y)) #:G33))
    (MUL (APPLY #'(LAMBDA (X Y) (* X Y)) #:G33))
    (DIV (APPLY #'(LAMBDA (X Y) (/ X Y)) #:G33))))

Note the technique of substituting expr directly into the
macroexpansion only once, binding it there to a temporary and using
that temporary whenever you want to refer to the value of expr.

If you want the more general destructuring provided by macros and your
Lisp has destructuring-bind, you could use:
(defmacro record-case (expr &body body)
  (let* ((expr-temp (gensym))
	 (expr-cdr-temp (gensym))
	 (new-body (mapcar #'(lambda (clause)
			       `(,(car clause)
				 (destructuring-bind ,(cadr clause)
				     ,expr-cdr-temp
				   ,@(cddr clause))))
		      body)))
    `(let* ((,expr-temp ,expr)
	    (,expr-cdr-temp (cdr ,expr-temp)))
       (case (car ,expr-temp)
	 ,@new-body))))
Now record-case expands to:
(LET* ((#:G41 X) (#:G42 (CDR #:G41)))
  (CASE (CAR #:G41)
    (ADD (DESTRUCTURING-BIND (X Y) #:G42 (+ X Y)))
    (SUB (DESTRUCTURING-BIND (X Y) #:G42 (- X Y)))
    (MUL (DESTRUCTURING-BIND (X Y) #:G42 (* X Y)))
    (DIV (DESTRUCTURING-BIND (X Y) #:G42 (/ X Y)))))

If your lisp doesn't have destructuring-bind or you don't want to use
the macro lambda list syntax, I can send you some code I wrote for
destructuring in the LOOP macro. However, implementing destructuring
(or a simplified version of destructuring-bind) is a fine project for
an aspiring hairy-macro writer!

Tim Moore                    moore@cs.utah.edu {bellcore,hplabs}!utah-cs!moore
"Ah, youth. Ah, statute of limitations."
		-John Waters