In broad terms, the evaluation of a Racket program proceeds in four stages:
A source file may have a reader attached (via the #lang line). If so, this reader transformation is applied to the code.
Any forms that introduce bindings, like require or define, are resolved. + For the specifics, see introducing bindings in the Racket Reference.
Macros are expanded, starting with the outermost forms and progressing inward. This stage of evaluation is known as compile time or phase 1. Macro expansion is recursive: if a macro expands to become another macro, the new macro is also expanded. Eventually, there will be no macros left, leaving only core syntactic forms. This is called the fully expanded program.
If any identifiers in the fully expanded program still don’t have bindings, then evaluation fails with an error. Otherwise, all expressions in the fully expanded program are evaluated, starting with the innermost expressions and progressing outward, as in the example below. This stage of evaluation is known as run time or phase 0. Whatever remains is the program result.
1 2 3 4 5 6 7 8 9 |
This process is recursive: each phase of evaluation can trigger the import of other modules (for instance, using require). This, in turn, causes the evaluation of these imported modules.
Run-time expressions are evaluated from the inside out. But macros are evaluated in the opposite direction—from the outside in. The necessity for this should be apparent: a macro can rewrite any code inside it, so there’s no way to know in advance whether the inner expressions can or should be evaluated at all. Consider this example—do you end up with a zombie apocalypse, or baby pandas?
1 2 | (define-macro (unleash INNER-EXPR) #''baby-pandas) (unleash (zombie-apocalypse)) |
But this difference in evaluation order can also defeat certain intuitions about expressions, especially when nesting macros inside each other, or using macros that have side effects. Here, notice how using functions instead of macros changes the output:
1 2 | inner func outer func |
1 2 3 | (define-macro (omacro INNER-EXPR) #'(displayln "outer macro")) (define-macro (imacro) #'(displayln "inner macro")) (omacro (imacro)) |
1 | outer macro |
Macros explainer
Modules explainer
Evaluation model in the Racket Reference
Fully expanded programs in the Racket Reference. Recommended for supernerds: shows you how the entire Racket language—and therefore, all languages implemented with Racket—get converted to an elegantly small vocabulary of core syntactic forms.