~ / track B / clojure advanced
Macros
AdvancedA macro is a function that runs at compile time and returns code instead of a value. Because Clojure code is data (homoiconicity), macros let you grow the language: anywhere a Clojure expression goes, you can substitute one your macro built from the user's syntax. With great power comes the rule: don't write a macro when a function would do.
Minimal example
A macro receives its arguments unevaluated as data — typically lists — and
returns a new form. Quoting (') and syntax-quote (`) are the tools you
use to assemble that form.
~ (unquote) injects an evaluated expression into a syntax-quoted template,
and ~@ (unquote-splicing) inlines a sequence as multiple forms. The expansion
of the call above is:
(if (pos? 5) (do (println "yes") :ok) nil)
See the expansion
macroexpand-1 runs your macro once and hands you back the code it produced.
This is the single most useful macro-debugging tool:
Practical example: a tiny debug printer
The classic macro use-case is wrapping a piece of code with extra behavior without changing how it's written:
Two details to notice:
v#is an auto-gensym: Clojure replaces it with a fresh, collision-free name. Use this for any local you introduce in a macro so you don't shadow bindings from the call site.'~formquotes the original form (so it prints as the user wrote it) whilev#carries the evaluated value.
When NOT to write a macro
Macros are non-composable: you can't map them, you can't pass them to
higher-order functions, and they cost users a layer of indirection when
reading code. Reach for a function whenever the work fits, and use a macro
only when you need to:
- delay or skip evaluation (e.g.
when,or), - introduce new binding forms (e.g.
let,with-open), - compile away boilerplate that a function literally can't (e.g.
defrecord).
Check yourself
? quiz
`(defmacro twice [x] `(do ~x ~x))` then calling `(twice (println :hi))` prints `:hi` how many times?
Exercise
Write a macro unless that behaves like the inverse of when: it should
evaluate its body only if the test is false.