One of a series of tutorials about Scheme in general
and the Wraith Scheme interpreter in particular.
Copyright © 2011
Jay Reynolds Freeman,
all rights reserved.
Personal Web Site: http://JayReynoldsFreeman.com
The title of this tutorial is based on the fact that there are two kinds of macros available in Wraith Scheme. One is the "hygienic macro" implementation that is required by the R5 Scheme standard. The requirement for hygienic macros is relatively new to Scheme. Hygienic macros will be described in another tutorial.
Wraith Scheme also has an additional macro implementation installed, one that predates the introduction of hygienic macros into Scheme. This older system is non-hygienic, and is also "dirty" in the sense of "quick and dirty" -- it is about the simplest kind of Lisp macro implementation that you can imagine. Hence the present title.
The intent of a programming-language macro system is to transform source code before it is compiled or interpreted. Macro preprocessors in C, C++, and in various assembly languages typically use a simple substitution language to transform text strings. The Unix macro processor, "m4", does the same thing with a bit more power and flexibility. Macro processors for Lisp systems generally do something different: They usually deal with source code at the level of S-expressions, not text -- that is, what is to be transformed is something like the Lisp object
'(this that the-other)
-- note the quote -- and not the text string
"(this that the-other)"
Wraith Scheme's older macro system is indeed of this latter kind. It provides a mechanism to transform a Scheme object before the regular Wraith Scheme evaluator gets a chance to interpret it. Thus if the symbol "foo" is bound to a Wraith Scheme macro of the older kind, then the macro "call"
(which looks just like a procedure application but is not, because "foo" evaluates to a macro, not to a procedure), results in "(foo something)" being somehow transformed into "something else", whereupon the Wraith Scheme interpreter gets to evaluate
The way that happens is that the entity that symbol "foo" is bound to -- the macro itself -- is in essence a lambda expression by another name. When the Wraith Scheme interpreter evaluates "foo" (as the first part of evaluating "(foo something)", it finds that "foo" evaluates to a macro, and on that basis it applies the lambda expression in question to the entire unevaluated form, "(foo something)", and then evaluates whatever the lambda application returns. For this to work, the lambda expression in question must take one argument; that is a requirement for old-style Wraith Scheme macros.
The basic idea here is really simple: When you use an old-style Wraith Scheme macro, you get to use a lambda expression of your choice to modify the expression that invoked the macro, before that expression gets evaluated.
Let's look at that again in a bit more detail. The Wraith Scheme evaluator sees the expression
It first evaluates "foo" -- recall that the evaluator always has to evaluate the first item in such cases, in order to know what to do with the rest of the arguments. The result is a macro, and on that basis the evaluator knows what to do. The macro is specially-identified lambda expression. The evaluator applies that lambda expression to the entire original expression, "(foo something)", without evaluating any of it. That is a little hard to write out with the notation we have been using, but the following will suggest what is going on:
(the-lambda-expression '(foo something))
The lambda expression can do whatever it wants to the original expression -- it can transform it in any way. It can even ignore the expression entirely, and return something else. Ultimately, though, it must return something:
(the-lambda-expression '(foo something)) ;; ==> something-else
The Wraith Scheme evaluator does not pass "something-else" back to the original invoker of "(foo something)". Instead, the evaluator evaluates "something-else", and passes the result of that second evaluation back as the result of invoking macro "foo". If we put that all together, what gets passed back as the result of the macro application "(foo something)" is in essence
(evaluate (lambda-expression-bound-to-foo '(foo something)))
Actually, the procedure used is not called "evaluate", it is Wraith Scheme's more awkwardly named "e::cons-with-continuation", but you might not remember that procedure from the tutorial where it is described. In any case, the returned value from the lambda expression does get evaluated, and the evaluation is performed in the environment in which the macro application, "(foo something)" was first made.
Wraith Scheme has a number of special procedures and forms to help you work with old-style macros. They are described in more detail in the Wraith Scheme Help File and in the Wraith Scheme Dictionary, but here are a few to get you going:
Special form "e::macro" works rather like the simple form of "define". That is
(e::macro foo <something>) ;; ==> foo
declares that <something> is a macro -- and it had better be a lambda expression that takes one argument -- and binds it to symbol "foo". On the other hand, "e::macro-body" simply declares that what follows it is a macro, and returns that macro:
(e::macro-body <something>) ;; ==> #
That means that the following two Wraith Scheme expressions are precisely equivalent:
(e::macro foo <something>) ;; ==> foo (define foo (e::macro-body <something>)) ;; ==> foo
Procedure "e::expand-macro" expands a macro, so that you can see what transformation is performed on the original expression. In many circumstances, its argument must be quoted. We will see an example of its use soon.
Here is a simple example, which is also given in the Wraith Scheme Help File. Suppose you are tired of writing assignment statements in the form "(set! foo bar)", and would rather write them as "(assign bar to foo)". Define the macro:
(e::macro assign (lambda (form) `(set! ,(cadddr form) ,(cadr form))))
You can then write
(define foo #f) ;; ==> foo (define bar 3) ;; ==> bar (assign bar to foo) ;; ==> foo
The last expression assigns bar, which evaluates to 3, to foo. Now
foo ;; ==> 3
Let's look closely at how that works. The macro call "(assign 3 to foo)", causes the entire expression, "(assign 3 to foo)" to be passed as the argument "form" to "(lambda (form) `(set! ,(cadddr form) ,(cadr form))))". That procedure returns a list whose first element is "set!", whose second element is the cadddr of the form (namely the symbol "foo"), and whose third element is the cadr of the form, namely the number 3. That list, "(set! foo 3)" is in turn evaluated, causing 3 to become the value of foo.
You can see what is happening by evaluating
(e::expand-macro '(assign 3 to foo)) ;; ==> (set! foo 3)
This expression causes only the first two parts of the macro call "(assign 3 to foo)" to take place: It returns the unevaluated result from the procedure, namely the list "(set! foo 3)".
Remember, the basic idea of Wraith Scheme's older style of macros is very simple: When you use such a macro, you get to use a lambda expression of your choice to modify the expression that uses the macro before that expression gets evaluated. Yet remember also, there is an old saying among computer programmers: "It is possible to be entirely too clever with macros." You must decide how to make the tradeoff, in the way that is best for you.
-- Jay Reynolds Freeman (Jay_Reynolds_Freeman@mac.com)