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