In broad terms, the evaluation of a Racket program proceeds in four stages:
A may have a attached (via the #lang line). If so, this reader transformation is applied to the code.
Macros are expanded, starting with the outermost forms and progressing inward. This stage of evaluation is known as or . 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 . This is called the .
If any in the fully expanded program still don’t have , then evaluation fails with an error. Otherwise, all 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 or . 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?
(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:
inner func outer func
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 .