A macro is a code processor: it consumes program fragments as data and emits new program fragments — syntax in, syntax out — before ordinary evaluation runs on the result. Below: the three-step model (resolve macro → expand with unevaluated operands → evaluate the generated expression).

Philosophy — macros as code processors

The mental model that ties everything together: a macro is not "a fancy function that returns a value." It is a transformer of code — often described informally as something that eats code and spits code, or more precisely ingests unevaluated expressions and emits new expressions (still represented as lists and symbols, i.e. as Scheme data).

So the macro runs in a different phase from the rest of the program: it is a small program whose job is to rewrite another program. Helpers like list, cons, and quasiquote are just the tools for building that emitted code.

📌 Note

Same slogan everywhere: code in → code out. Every example below — twice, repeat, for — is the same pattern: read syntax as data, assemble different syntax, then let evaluation handle the rest.

Running example

Suppose we define this macro:

(define-macro (twice expr)
  (list 'begin expr expr))

and then call:

(twice (print 'hi))

The following sections break down exactly what happens — each step is part of feeding code into the macro machinery and getting new code back.


Step 1 — Evaluate the operator to obtain a macro

The interpreter sees (twice (print 'hi)) and first evaluates the operator position: twice. The environment lookup does not bind twice to an ordinary lambda; it binds it to a macro procedure.

That distinction matters because the interpreter must choose a path: ordinary function call versus macro expansion. Once it knows twice is a macro, it follows the macro-specific rules instead of the usual call-by-value sequence.

📌 Note

Resolving the operator is still normal evaluation — only after that does the implementation decide whether operands are evaluated before or after handing them to the callee.