Learn how auto-gensyms, explicit gensyms, and qualified symbols prevent macro-generated locals from colliding with caller code.
Macro hygiene means generated names do not accidentally capture or collide with names in the caller’s code. Hygiene is not style polish. It is what keeps a macro from changing behavior when a caller happens to use the same local name.
This macro introduces a local named value:
1(defmacro bad-measure
2 [& body]
3 `(let [value (do ~@body)]
4 (println "value:" value)
5 value))
The generated value is a name chosen by the macro author. In real macros, manually chosen generated names can collide with caller locals or become confusing once syntax quote qualifies symbols.
The safer pattern is to generate a unique local:
1(defmacro measure
2 [& body]
3 `(let [value# (do ~@body)]
4 (println "value:" value#)
5 value#))
Inside syntax quote, value# becomes an auto-gensym: a unique symbol for this expansion.
| Tool | Use it when |
|---|---|
name# |
You need a generated local inside one syntax-quoted template. |
(gensym "prefix") |
You need to compute or reuse a generated symbol across multiple templates. |
| Qualified symbols | You need generated code to refer to a stable function or var. |
Most practical macros start with auto-gensyms. Use explicit gensym only when one generated name must be shared across separately built forms.
1(defmacro with-timing
2 [label & body]
3 `(let [started# (System/nanoTime)
4 result# (do ~@body)
5 elapsed# (- (System/nanoTime) started#)]
6 (println ~label "took" elapsed# "ns")
7 result#))
The generated locals started#, result#, and elapsed# cannot collide with caller locals. The caller’s label and body are still inserted where intended.
1(let [result "caller local"]
2 (with-timing "work"
3 (str result " done")))
The caller’s result remains separate from the generated result#.
| Check | Good answer |
|---|---|
| Does the macro introduce locals? | Yes, and each generated local is auto-gensymed or explicitly gensymed. |
| Are caller forms inserted only where intended? | Yes, with ~ or ~@ at controlled points. |
| Are core functions resolved predictably? | Yes, syntax quote qualifies them or the code uses explicit namespaces. |
| Can the expansion be read without guessing names? | Yes, generated locals are boring and limited. |
| Java-generated-code concern | Clojure macro hygiene concern |
|---|---|
| Generated fields conflict with user fields. | Generated locals collide with caller locals. |
| Generated source imports the wrong class. | Generated symbols resolve in the wrong namespace. |
| Templates use hidden temporary variables. | Macros introduce unexplained temporary symbols. |