Scheme Programming/Macros

Macros are said to be what makes Lisp Lisp. Other programming languages, notably C and C++, have macros, but they're not like Lisp macros. In C, the macro preprocessor allows you to do limited textual substitution on snippets of C code. Sometimes, this produces working C code, while other times it can result in invisible misplaced braces or semicolons that the compiler can't quite pinpoint.

Lisp macros are full-blown Lisp procedures, with the full power of any other Lisp procedure. Instead of text, they get lists that represent the bits of code that you want to change. The return value of a macro is a list representing the new program. Scheme supports three types of macro. When it comes to understanding what's going on, the easiest of these three types of macro is Lisp's original type of macro, known as  in Common Lisp, EMACS Lisp, and SCM. Other Scheme implementations call this form, while still other implementations, notably Racket, reject it as being too primitive.

To use macros in SCM, you must first evaluate this form:

Other Scheme implementations generally do not require this step.

A simple macro can illustrate what's going on. We want it to do something that would be impossible to do with a procedure. So we'll implement a macro that works like a `begin` form, but runs the code within it in reverse order:

Every time you write this:

...the macro code above gets called at compile time, and the code inside the  block is passed as the   argument. The macro reverses this code and conses the symbol  to the beginning of the result, creating a   form:

Your program will execute as if that begin form was what you wrote in your program. Macros can do absolutely anything to the code in their arguments. Here is a more useful macro:

Then you can write this program:

The while loop checks if the conditional expression is true, and if it is, it executes the body over and over until the condition becomes false. In the above code, the condition becomes false when  reaches a value of 0.

There is just one small problem with. The  procedure that our while loop uses for recursion is visible to the code in the body when you use a while loop. You could set it to something else, and then the loop would break. You might do this accidentally:

Lisp provides a solution to this, called  in Common Lisp and most Scheme implementations. However, in SCM, this procedure is called. It generates a symbol that is guaranteed not to appear anywhere else in the program. You can use it to generate a name to use instead of. Fixing the while macro, it now looks like this:

Hygienic Macros: syntax-rules
Scheme introduces hygienic macros to the Lisp world. When writing this kind of macro, it is impossible to accidentally introduce variable names that the code you're altering can change. However, to write them, we must abandon the concept of code being made out of simple lists.

There are two kinds of hygienic macros, but only the less powerful of the two is supported by SCM. Syntax-rules macros rely on pattern-matching to define the valid ways to invoke the macro, and templates to define the output code. There are some macros that cannot be defined with. Here's the while loop again, defined with  instead of  :

If you refer to  within the body if this version of , it refers to a different   from the one shown in the template. It is impossible to define a variable with  that code from outside the macro can see.

The form that says  is the pattern. is a wildcard. Anything can match it, and its value is not bound to anything. In this case, it is standing in the place where the word  goes. and  are pattern variables. They're not real variables. They won't have value at runtime, only within the template. Due to dotted-list notation,  is matched in the   position, which means it's all the forms that come after the.

Scheme's  form can be implemented with this macro system. In most implementations,  is in fact a macro. A typical definition looks like this:

works like Racket's version of  in that   is legal and has an unspecified return value, and unlike SCM's   in which   is illegal. The  at the top in parentheses is the list of literals. They have to match exactly in the code in order to match the pattern. In other words, where you see  in the pattern, it only matches if the word   is found there.

Now let's make a REAL macro
What if you could pattern-match regular lists at runtime in the same manner that you can pattern-match syntax at compile-time when using ? Then, if you had a list and you wanted to capture the first two elements (or do something else if it wasn't really a list or if it didn't have two elements), you could write something like this:

The macro supports literals just like. The only thing that's not supported is the wildcard operator. Every value matched will be bound to something.

We'll use  because in the end, the pattern matching provided by it does more to make this job easier than does the Turing completeness of. When we're done, we'll be able to use the same kind of pattern matching in macros as well, by using this macro within its body. The only thing we'll be missing in  then will be hygiene.

How pattern matching works
Implementing something like this using  will require two macros. One macro will match a single pattern, and evaluate an expression if it succeeds, or return a failure value if it fails.

The  macro requires five arguments:

The  contains the names of variables in the positions you hope to find them in some candidate structure. So if the pattern is, for example, then you're trying to bind   to the   of a list, and  to its. If  and   are not literals, then this pattern can be matched against   by generating the following code:

is a special version of  that won't raise an error if it's called on a non-pair. Instead of assuming that will be a list,  makes it possible to check, without us having to write   every time.

If the  of the list matches, the   macro is used again on both the   of the data and the   of the pattern (which is   in the code snippet above). This way,  can be either another pattern, or a variable.

If  or   encounter something that isn't a list, they return , which we then check for and return if it's found. That way, if any call to  or   evaluates to , then the entire   also evaluates to.

The macro will use  from the /Looping/ chapter to implement the literals. If an identifier in the  is found in the , then that identifier is interpreted as a symbol, and that position in the structure must be that identifier or   will be returned. The  will be provided from outside the macro. Ultimately, we'll generate a failure value at runtime using  (or  ) on any Scheme implementation except SCM).

This example shows what it does:

The second case fails because  is defined as a literal, so there must be an actual   symbol in there for the pattern to match.

The failure-value makes it possible to evaluate a chain of these in a  form, which makes it possible to write a multi-pattern match macro that mirrors.

An example:

To demonstrate the use of this macro in conjunction with, let's define   again using  :

And that is what macros can do.