Reading — step 1 of 5
Learn
~1 min readMacros and Dynamic Vars
Clojure's killer feature: code is data. Macros are functions that take and return code (s-expressions), running at compile time.
(defmacro unless [test then else]
`(if (not ~test) ~then ~else))
(unless (zero? 5)
(println "non-zero")
(println "zero"))
;; expands at compile time to:
;; (if (not (zero? 5)) (println "non-zero") (println "zero"))
Quasiquote / unquote (same as Common Lisp):
`— quasiquote: build a code template~— unquote: insert the value here~@— unquote-splice: insert and flatten a list
gensym / auto-gensym for hygiene — avoid name capture:
(defmacro swap [a b]
`(let [tmp# ~a] ;; tmp# generates a unique symbol
(def ~a ~b)
(def ~b tmp#)))
;; The trailing # is auto-gensym — only valid inside quasiquote.
macroexpand — see what your macro generates:
(macroexpand-1 '(unless true (println "a") (println "b")))
;; => (if (not true) (println "a") (println "b"))
(macroexpand-1 '(when (> x 0) (foo) (bar)))
;; => (if (> x 0) (do (foo) (bar)))
Useful macro patterns:
- Control flow:
when,cond,caseare all macros - Resource management:
with-openensures cleanup - Threading:
->,->>,as->,cond-> - DSLs: re-frame, hiccup, datomic queries
When to write a macro:
- You need to take code (not just values) — e.g., delaying evaluation
- You're hitting boilerplate that no function or higher-order function can eliminate
- Building a small embedded language
When NOT to:
- A function would do — functions compose better, debug easier
- Performance — compile-time, but harder to optimize than runtime polymorphism
- Most production code: <5% macros, >95% functions
Clojure conventions: a macro's name often hints at it (with-, def-, if-).
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…