Subject: Re: Macros using local variables
From: rpw3@rigden.engr.sgi.com (Rob Warnock)
Date: 1999/10/07
Newsgroups: comp.lang.scheme
Message-ID: <7tho3k$546j@fido.engr.sgi.com>
Thore Bjerklund Karlsen <sid6581@mail.utexas.edu> wrote:
+---------------
| Should Scheme macros be able to use local variables inside the function from
| which they are invoked?
+---------------

Yes.

+---------------
| I just can't get it to work in DrScheme. Here is an example:
|   > (define-macro returnxy (lambda (x) (* x y)))
|   > (define (y4 y) (returnxy 4))
|   reference to undefined identifier: y
|   >
| Am I doing something wrong, or is it just impossible?
+---------------

The former. The defining value of a MzScheme "define-macro" form is
an ordinary procedure object, whose free variables are closed over
when the procedure is created -- which in the example above is when
the macro is *defined*, not used. That is, except for introducing
an extra global variable, your example:

	> (define-macro returnxy (lambda (x) (* x y)))

could just as well be written as this:

	> (define returnxy-expander	; or equivalently:
	    (lambda (x)			;  (define (returnxy-expander x)
	      (* x y)))			;    (* x y))
	> (define-macro returnxy returnxy-expander)

Already we have a problem, namely, that the procedure "returnxy-expander"
depends on a variable "y" in the global environment. And if nobody defined
a global variable "y" by the time we call it, we get the same error you got
[without even calling the macro itself yet!]:

	> (returnxy-expander 4)
	reference to undefined identifier: y
	> 

Let's step back a bit. What are macros really supposed to *do*, anyway? Not
compute "results", but compute *forms* that replace the macro call form at
expansion time. So if you want (as in your example) the form "(returnxy 4)"
to expand to the form "(* 4 y)", then that S-expression -- a 3-element list
containing a symbol, a number, and another symbol -- is what the procedure
"returnxy-expander" is going to have to return. So let's try it this way:

	> (define returnxy-expander
	    (lambda (x)
	      (list '* x 'y)))

Now when we call the expander, we get back another (computed) form:

	> (returnxy-expander 4)
	(* 4 y)
	> (returnxy-expander 17)
	(* 17 y)
	> 

Putting it all together:

	> (define returnxy-expander
	    (lambda (x)
	      (list '* x 'y)))
	> (define-macro returnxy returnxy-expander)
	> (define (y4 y)
	    (returnxy 4))
	> (y4 5)
	20
	> (y4 5)
	28
	>

So when MzScheme starts to compile "(define (y4 y) (returnxy 4))",
it first expands it (by calling the expander for "returnxy") into
"(define (y4 y) (* 4 y))", and *now* the inner "y" is in an environment
that contains a binding for "y"!

Does that help?


-Rob

p.s. Once you've got macro-define-time and expansion-time environments
clearly sorted out, you can go back to making the expander procedure be
"anonymous" (an unnamed closure), if you like:

	> (define-macro returnxy
	    (lambda (x)
	      (list '* x 'y)))

Or even use the more compact (but also trickier to understand sometimes)
"quasiquote" (a.k.a. "backquote") notation:

	> (define-macro returnxy
	    (lambda (x)
	      `(* ,x y)))

p.p.s. Here's a macro that defines the Common Lisp "defmacro" form
[which, personally, I prefer to use over any of the various idiosyncratic
forms of low-level Schme macros] in terms of MzScheme's "define-macro":

	> (define-macro defmacro
	    (lambda (name args . body)
	      `(define-macro ,name (lambda ,args ,@body))))
	> 

Now here's your example written in "defmacro" style:

	> (defmacro returnxy (x)
	    `(* ,x y))
	> (define (y4 y)
	    (returnxy 4))
	> (y4 3)
	12
	> 

Figuring out how this works might be a good next step in your "macrology".

-----
Rob Warnock, 8L-846		rpw3@sgi.com
Applied Networking		http://reality.sgi.com/rpw3/
Silicon Graphics, Inc.		Phone: 650-933-1673
1600 Amphitheatre Pkwy.		FAX: 650-933-0511
Mountain View, CA  94043	PP-ASEL-IA