# Macros
Deft macros run at compile time, receive their arguments unevaluated,
and produce code that's spliced into the call site. They're the
language's main code-generation tool — used for control structures,
DSLs, and eliminating boilerplate.
## Defining a Macro
`defmacro` follows the same `|params| body` syntax as `def`. The body
runs at compile time and returns Deft code.
defmacro double {|x| ($x + $x)}
When you call `double 5`, the compiler expands the body to `(5 + 5)`
and compiles that in place.
### Parameters
Macro parameters are **unevaluated**. The parameter value is the
literal AST node from the call site:
defmacro when {|cond body| [if $cond $body]} when ($n > 5) { echo "big" } # expands to: # if ($n > 5) { echo "big" }
### Variadic macros — `@rest`
The `@` prefix collects remaining args into a list. The `@` mirrors
the `@{...}` vector literal — it signals "list-collecting param".
Must be last. The name after `@` is bound verbatim — pick whatever
reads best at the call site:
defmacro variadic-sum {|@nums| [reduce $nums 0 {|a b| ($a + $b)}]} variadic-sum 1 2 3 4 # → 10
Works in plain `def` too, and combines with defaults:
def f {|a b=10 @rest| ($a + $b + [len $rest])}
## Quote and Unquote
`[quote ...]` builds code while **preserving** `$param` references as
literal symbols. `[unquote $param]` is the escape — it substitutes the
parameter's value into the quoted form. Both must use `[...]` brackets
(deft's `( ... )` parens are infix-only, not calls).
defmacro unless {|cond body| [quote [if [unquote $cond] nil [unquote $body]]] } unless false { echo "ran" } # expands to: [if false nil { echo "ran" }] # → prints "ran"
Most macros don't need explicit `quote` — argument splicing via `$a`
already produces the right code. Reach for `quote` when you're
generating new code from scratch or want a `$param` to survive as a
literal symbol into the expansion.
### `[syntax-quote ...]` — hygienic code generation
Like `quote`, but:
1. `$param` references **are** substituted (no need for `[unquote ...]`
around parameters).
2. Bare symbols (e.g. `tmp`) are automatically renamed to unique
gensym'd names, so they can't capture caller variables.
defmacro swap {|a b| [syntax-quote { set tmp $a # ← 'tmp' auto-gensym'd to tmp#N set $a $b set $b $tmp }] } set tmp "outer" [swap x y] echo $tmp # still "outer" — safe
### `[splice $list]` — inline list elements
Spread a list's elements into the parent form during macro expansion:
defmacro call-with {|func @args| [quote [unquote $func [splice $args]]] } [call-with str "a" "b" "c"] # expands to [str "a" "b" "c"]
(Don't confuse with `..$xs`, the **runtime** spread operator for
function calls — that's a separate feature.)
## Common Patterns
### Custom control structures
defmacro unless {|cond body| [if $cond nil $body]} unless ($n == 0) { echo "non-zero" }
### Block bodies in the middle of a macro
A `{...}` block argument is inlined where `$body` appears, even when
it's not the last statement — the macro runs it in place:
defmacro sandwich {|body| set before "before" $body set after "after" } set inside "" [sandwich {set inside "inside"}] # inside == "inside"
### Loops
defmacro my-repeat {|n body| for _ [range $n] $body } my-repeat 3 { echo "hi" }
### Function generation
defmacro make-adder {|n| def adder {|x| ($x + $n)} } make-adder 10 adder 5 # => 15
### `[begin e1 e2 ...]` — sequencing
Evaluates each expression in order, returns the last. Multi-statement
`{...}` blocks are auto-wrapped in `begin`, so explicit use is rare —
most useful inside `quote`/`syntax-quote` for multi-step expansions:
defmacro inc-and-return {|x| [quote [begin [set $x ($x + 1)] $x]] }
## Compile-time conditionals — `compile-when`
`[compile-when cond body]` evaluates `cond` at compile time (through
the VM) and only emits `body` if truthy. If falsy, `body` is discarded
— the call site compiles to a single `push_null`, producing **zero
runtime code**.
This is distinct from `if`, which always compiles both branches and
picks at runtime. `compile-when` is for cases where the decision is
known at compile time and the rejected branch should carry no cost:
level-filtered logging, debug-only assertions, feature-flagged code
paths.
def log-level ([os/env "DEFT_LOG_LEVEL"] or "debug") defmacro debug {|@args| [compile-when [in? @{"debug"} $log-level] [log/debug [splice $args]]] }
With `DEFT_LOG_LEVEL=error`, every `[debug ...]` call site compiles to
`push_null` — the `log/debug` call never enters the bytecode.
The condition must be evaluable at compile time: literals, globals
from `require`d modules, and pure functions of those. It cannot
reference runtime locals or closure captures. See
[compile-when](../../docs/compile-when) for full details.
## Hygiene
Deft macros are **not** hygienic by default — a symbol a macro
introduces will collide with a same-named symbol at the call site. Two
tools fix this:
- `[gensym "prefix"]` — returns a unique keyword (`:prefix#N`). Useful
as an opaque runtime token or map key. **Cannot** become a variable
*name* (deft `set` takes a literal identifier).
- `[syntax-quote ...]` — auto-gensyms bare symbols at compile time, so
generated identifiers stay fresh. This is the right tool for
compile-time hygiene.
## Built-in Macros
Several built-in forms are implemented as macros or compile-time
special forms:
- `defsuite` / `deftest` — test scaffolding (see [Testing](testing))
- `def` / `defn` / `defcoro` / `deftask` / `deftype` / `defprotocol` /
`defimpl` / `defmethod` / `defmacro` — all definition forms
- `if` / `for` / `while` / `match` / `try` / `catch` / `defer` — control flow
- `quote` / `unquote` / `syntax-quote` / `splice` / `begin` — code generation
- `compile-when` — compile-time conditional (elides rejected branch)
## When To Use Macros
Macros add power but reduce readability. Reach for one when:
- You need a new **control structure** that doesn't fit function-call
semantics (`unless`, `when`, `cond`).
- You need to **delay evaluation** of arguments in a way closures
can't express cleanly.
- You're building a **DSL** that should look like syntax (HTTP
routing, test scaffolding, HTML blocks).
Don't write a macro when a function would do:
# ✗ Unnecessary macro defmacro add {|a b| ($a + $b)} # ✓ Just a function def add {|a b| ($a + $b) }
## Debugging Macro Expansion
To see what a macro expands to, use `info/sig` or `info/source`:
[info/sig $when] [info/source $when]
For full expansion at a call site, wrap the call in `quote` and
pretty-print:
[pretty [quote { when ($x > 5) { echo "big" } }]]
## Limitations
- Macros cannot recurse beyond the compiler's expansion limit (~64 levels).
- Macros cannot access runtime state — they run at compile time.
- Macro bodies may call other macros; expansion is recursive.
- A macro must return a valid Deft expression. Strings, numbers, and
other non-AST values will cause a compile error.
## Quick Reference
| Form | Purpose |
|---|---|
| `defmacro name { \|params\| body }` | Define a macro |
| `@rest` | Variadic (rest-collector) parameter — name after `@` is bound verbatim |
| `[quote expr]` | Build code, preserving `$param` as literal |
| `[unquote $expr]` | Substitute inside a `quote` |
| `[splice $list]` | Inline a list's elements into the parent form |
| `[syntax-quote expr]` | Hygienic quote — substitutes `$param`, gensyms bare symbols |
| `[gensym "prefix"]` | Generate a unique `:prefix#N` keyword |
| `[begin e1 ... en]` | Sequence expressions, return last |
| `[compile-when cond body]` | Compile-time conditional — elides `body` if `cond` is falsy |
| `[info/sig $macro]` | Inspect a macro |
| `[info/source $macro]` | View source |
See [Metaprogramming](metaprogramming) for the full reflection /
metadata API.