A macro is a special kind of function that runs at compile time, consuming certain code as input and rewriting it as new code. Macros are also known as syntax transformers.
For instance, and is a macro:
That rewrites its input like so:
The code resulting from a macro is called the expansion of the macro, because it’s often longer than the input code (though it can be any length).
Even though macros are functions, it’s conventional to refer to them strictly as macros, and reserve the term function for the traditional kind of function that’s evaluated at run time. Macros are also called syntactic forms or just forms (especially in the Racket documentation).
Macros are valuable for two reasons:
At the program level, macros allow us to implement ideas in code that can’t be achieved with ordinary functions.
For instance, let’s figure out how to make report, which prints an expression, and then returns the result of the expression normally.
Suppose report were an ordinary function. The expression (* 1 2 3 4) would be evaluated as 24, which would become the input to report. But then the original expression couldn’t be printed, because report would never see it. So report can’t be a function.
Whereas if report is a macro, its input includes the literal code (* 1 2 3 4), which can be manipulated as code:
1 2 3 4 5 6
Now if we run report, we get the right result:
input was (* 1 2 3 4) 24
In this way, macros resemble tools like the C preprocessor. But unlike a preprocessor, which usually involves a limited vocabulary of operations, a macro can use everything in the Racket language.
At the compiler level, macros allow forms to be expressed in terms of progressively more primitive forms, all the way down to a set of core syntactic forms. By only handling this small set of core syntactic forms, Racket can compile, optimize, and evaluate programs more efficiently. For instance, we saw that and is expressed in terms of if. For that matter, so are when and or. In turn, if is a core syntactic form.
Within a Racket-implemented programming language, we often use macros within our expander to convert the forms of our language into standard Racket forms. But really, we’re just stacking more macros atop an existing tower of macros, all of which lead back to the core syntactic forms.
Macros are functions that take one syntax object as input and return another syntax object. These syntax objects contain literal code, packaged with metadata like lexical context and source location. + To get around the limited interface, macros can read and write arbitrary syntax properties to pass extra metadata or arguments to other macros. See syntax objects for the whole story.
The syntax object passed to a macro contains the whole calling expression that invoked the macro. So if we invoke and like this:
and does not get three arguments as input, the way an ordinary function would. Rather, it gets a syntax object like this, which retains a reference to the lexical context of the calling site:
(and #'(and (expr a) (expr b) (expr c)))
A macro extracts the pieces of the input syntax object and rearranges them into new code. Usually this is done with the help of syntax patterns, which match these pieces to pattern variables. For example, we could implement and as shown below, using three syntax patterns to handle input cases of zero arguments, one argument, and two or more arguments. The pattern variables are capitalized:
1 2 3 4 5 6 7 8 9 10
The expression on the right side of each branch is the output code for that branch, using #' to package it as a syntax object. References to pattern variables are automatically replaced with the matched item.
Unlike nested expressions, which are evaluated from the inside out, nested macros are evaluated from the outside in. This makes it possible for macros to generate references to other macros, which in turn are expanded. For instance, the third branch of and includes a recursive reference. + As with ordinary functions, it’s possible to accidentally create macros with infinite recursion.
Because macros are expanded at compile time, they can’t access the run-time meanings of the input code (because while the macro is running, those meanings have yet to be established). Therefore, a macro has to handle its input arguments strictly as syntactic items.
Macros aren’t a substitute for ordinary functions. Macros are inherently more limited:
They perform one service: rewriting code.
They can only consume and return syntax objects.
They run at compile time, so they can’t know anything about the run-time meaning of their input.
Moreover, because macros can rewrite code in arbitrary ways, they can make code less readable and predictable. It’s tempting, for instance, to infer that this line of code creates a variable named fruit with the value "banana":
(define fruit "banana")
But we’d be assuming that in this language, define has its usual meaning. It might not. What if our language redefines define with a macro?
1 2 3
(define-macro (define NAME VALUE) #'"orange you glad I didn't say banana?") (define fruit "banana")
Early-stage Racketeers are sometimes tempted to use macros to avoid learning the idioms of the core Racket library. Why? Because writing macros is fun; learning the library, less fun. But in the long term, it’s a false economy. You’ll want to understand Racket idioms because you’ll eventually need to read other Racket code (and others will need to read yours).
Macros are powerful, but they’re not free. Expanding a macro is additional work that has to be done at compile time. A macro creates new code, which then has to be compiled and interpreted at run time.
In general, ordinary functions have better performance characteristics. Therefore, some tips for combining them efficiently:
Don’t use a macro where a function will work equally well. Save macros for the situations where a function won’t work.
Design the macro to emit as little code as possible. Where possible, refactor code into an external helper function. (Because of hygiene, this is easy to do.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Macros in the Racket Guide
Syntax model in the Racket Reference
Fully expanded programs in the Racket Reference