Macros and hygiene
A macro is a function from forms to forms that runs at compile time.
The compiler hands it a use of its name, it returns the code that use
stands for, and the compiler continues on the result. That is how
if, when, set!, class, and the -each family exist: each
lowers to a smaller core form.
What a macro is for
A function receives values after its arguments are evaluated. A macro receives the argument forms unevaluated, so it can decide whether, when, and how many times they run, and it can introduce bindings around them. Reach for one when you need a new binding shape, control over evaluation, or boilerplate that must wrap a body you wrote. When the inputs are values, write a function; it is easier to test, pass around, and explain.
Defining one
syntax binds a name to a transformer. syntax-rules matches the
use against patterns and fills a template:
syntax unless:
syntax-rules []:
unless test body ...:
if test:
None
else:
(begin body ...)
Two rules govern where the pieces may sit. An ellipsis in a pattern
matches zero or more forms and the same ellipsis in the template splices
them back, but the template is read by the layout reader first, so a line
holding only body ... reads as the call (body ...). A dotted pattern
written in s-expression form, (m a . rest), binds rest to the
remaining forms as a list, and a dotted template (f a . rest) splices
that list back; the tail after the dot must be a pattern variable. A
template ellipsis must follow an element that names a pattern variable
captured under an ellipsis; one that follows anything else is a compile
error, never an empty expansion. A literal ellipsis is written through
the escape (... TEMPLATE), inside which every ... is an element of
the output; this is Scheme’s escape (R7RS syntax-rules), and it is how
a macro writes a macro:
syntax def-lister:
syntax-rules []:
case (def-lister name):
(global syntax name
(... (syntax-rules [] [(name args ...) (#list args ...)])))
And a macro and its first use must be in separate compilation units, because the macro is registered when its defining form finishes compiling. Macros works both rules through an example.
Templates and procedures
syntax-rules and syntax-match bodies are templates: a shape with
holes that the matched pieces fill. macro-rules and macro-match
bodies are procedures: ordinary Koskari that runs at expand time and
returns a form. Use the procedural kind when the output depends on
inspecting the input: reordering entries, computing a name, classifying
arguments.
Inside a procedural body you work with the matched forms directly:
form.datumunwraps a matched form to its pair; the elements inside are raw symbols, keywords, and values.var [head [tp rest]] form.datumdestructures it;(car rest)takes the next element.(keyword? x)and(symbol? x)classify a token.Build the output with quasiquote, or with variadic
cons, whose last argument is the tail.To emit an identifier you compute, wrap it in
datum:(datum (symbol f"job/{tp.datum.base}")). That is how a dispatcher rewrites(job kafka ...)into(job/kafka ...)and lets the compiler re-expand it. The tag is a label, so the name is built from its base: when a template wrotekafka,strof it shows the expansion’s mark.To pass the caller’s code back out, emit the matched form itself, or re-wrap a piece you took apart with
datum, as in(datum (cons (car e.datum) (cdr e.datum))). A piece taken out with.datumand emitted bare counts as the macro’s own and is marked.
Hygiene
A name the template introduces on its own is kept apart from every binding at the use site. A name the caller supplies is bound as written. Hygiene covers the mechanism in full.
Each expansion marks the symbols its template wrote, and the compiler
binds and resolves a marked name under its own spelling, tmp#1a for
the tmp of one expansion. A marked name that nothing bound falls back
to the plain name, so a template’s cons is the caller’s cons. A
marked name passed into another macro keeps its mark, so a binder
written through a nested macro is still the template’s own, and a
marked name inside quote or a backtick comes out plain; #' and
#` are the forms that keep it, which a macro a template defines
needs. A printed form shows marked names as written, whether through
print, str, or macroexpand at the REPL; the mark shows only in
str of the marked symbol itself.
(datum expr) is the author’s control in a procedural macro: it tags
a fragment as having come from the input form rather than from the
template, so it is never marked. Everything else the procedure emits
is marked, including the caller’s code once .datum has taken it out
of its match. A template has the same escape for a name it shares with the
use site: (datumq name) writes name raw, unmarked. See
Hygiene. At expand time, captured
pattern-variable values are carried in Datum boxes, which the finalize
step peels before compilation. The Python and C types are under
koskari.hygiene and
koskari.lib.
A function body a template writes sees every definition the same
expansion makes around it, in either order, so a template may define
mutually recursive functions or use labels and letrec as written
source does.
Brackets and parens match the same shape
A [...] and a (...) read to the same pair structure (see Brackets
and the call convention), so a pattern
cannot tell one from the other. [formals] matches any one-element form,
including a plain (f x) call, so no two arms can branch on whether a
group was written with brackets or parentheses. A form that must carry a
literal bracket, such as lambda formals, has to require it in every arm,
spelling out an explicit [] for the empty case rather than treating it
as optional.
Macros expand before names resolve
Macros and special forms are resolved at compile time, before ordinary
name lookup. When the compiler sees a form in call position it first
checks whether the head is a macro or special; if so it expands, and only
the expanded result is subject to name resolution. A macro always wins
over a same-named value binding in call position – a local let,
var, def, labels, or flet binding, a module binding, or an
import cannot displace it.
(let []
(def caddr [n] 999)
(caddr (cons 1 2 3 4 nil)))
;; -> 3, not 999
caddr is a macro that expands to nested car and cdr, so the call
expands at compile time and the local def is never consulted in that
position. A name bound to a plain function has no such precedence and
shadows normally:
(let []
(def first [n] (* n 100))
(first 3))
;; -> 300; first is a runtime function, so the local binding wins
A helper name that collides with a macro or special (if, when, a
cad*r accessor) will not rebind it in call position. Rename the helper
if you need it callable. The colliding name is still usable as a value,
passed as an argument or stored in a variable; only the call-position
head goes through expansion.
Seeing an expansion
macroexpand expands a form fully and macroexpand-1 takes one step:
>>> (macroexpand '(unless ready (print "go")))
(cond [ready None] [else: (print "go")])
See also
Hygiene: marks,
quote-syntax,quasiquote-syntax, and the#'and#`prefixesMacros:
syntax,extend-syntax, and the rule formscompile/let-syntax: a local compile-time binding
Glossary: what match and datum mean in compile-time documentation