Macros
Python shapes programs with decorators, context managers, and metaclasses. Koskari adds a macro: a function that receives code before it runs and returns new code. A macro is not needed on day one; it is how a repeated shape becomes a word.
A route
Python
@app.get("/hello/{name}")
async def hello(name: str):
return {"message": f"hello {name}"}
Koskari
define-route "/hello/{name}" hello [name]:
#{"message": f"hello {name}"}
What makes define-route exist?
A syntax form. The rule names the shape to match and the code
to produce:
var app (fastapi.FastAPI)
syntax define-route:
syntax-rules []:
define-route path name args body ...:
def name args body ...
(app.add_api_route path (coroutine-fn name) methods: #["GET"])
Each use expands into a def and a registration call, at compile
time. Nothing about it is special at runtime.
What is ...?
An ellipsis in a pattern matches zero or more forms, and the same
ellipsis in the template splices them back. body ... is the whole
block under the header.
Why is the def in the template on one line?
The template is read by the layout reader before the ellipsis is
expanded, and a line holding body ... is two elements, so it would
read as the call (body ...). Keeping body ... on the header line
makes it part of the def form. Where a body has to sit under a
block header, spell it (begin body ...).
A command
Python
import click
@click.command()
@click.argument("name")
def greet(name):
print(f"hello {name}")
Koskari
define-command main "name" greet [name]:
print f"hello {name}"
And the macro behind it?
import argparse
syntax define-command:
syntax-rules []:
define-command cli-name option fn-name [value] body ...:
def fn-name [value] body ...
def cli-name []:
var parser (argparse.ArgumentParser)
(parser.add_argument option)
var options (parser.parse_args)
(fn-name (attr options value))
Two definitions come out: greet, the function you can call from
tests, and main, which parses arguments and calls it. Growing it,
with flags, defaults, and help text, is more arms in the same rule.
Why does the use site name main as well as greet?
Hygiene. A name the template introduces on its own, such as a def main written inside the rule, is marked so it cannot collide with the
caller’s names. A name the caller supplies is bound as written. Both
functions are meant to be called, so both names come from the use.
Function or macro
When is a function enough?
Whenever the inputs are values. A macro is for a new binding shape, for
controlling whether or when something is evaluated, or for boilerplate
that should wrap a body you wrote. slug is a function; unless is a
macro.
Python
# no equivalent: Python cannot
# add a statement form
Koskari
syntax unless:
syntax-rules []:
unless test body ...:
if test:
None
else:
(begin body ...)
Seeing the expansion
How do I see what a macro produced?
>>> (macroexpand '(unless ready (go)))
(cond [ready None] [else: (go)])
unless is a builtin, so that expansion comes from the builtin’s
template, a cond. The syntax unless above is a worked example
of writing the rule; the definition in force here is the builtin’s.
Keep a parenthesized use beside the layout use while you learn; the s-expression is the tree the macro sees.
Where must a macro live?
In a form compiled before its first use: an earlier top-level form in
the same file, or a module you import. A begin that defines a macro
and uses it in the same block fails, because the macro is registered
when its form finishes compiling.
The matching rules, hygiene, and the procedural macro-rules form are
in Macros and hygiene.