# 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.