Learn the basic macro mental model: Clojure code is data, macros receive unevaluated forms, and macro expansion produces ordinary Clojure that Java engineers can review like generated source.
A Clojure macro is a function-like definition that receives unevaluated forms and returns a new form. That returned form is what Clojure evaluates.
The important phrase is unevaluated forms. A function receives values after its arguments run. A macro receives the code before those arguments run.
Clojure source is made from the same data structures you already use: lists, vectors, maps, symbols, strings, numbers, and keywords.
1'(+ 1 2)
2;; => (+ 1 2)
3
4(type '(+ 1 2))
5;; => clojure.lang.PersistentList
The quoted expression is a list whose first element is the symbol +. Because code is represented as data, a macro can build or rewrite that data before evaluation.
| Question | Practical difference |
|---|---|
| When do arguments evaluate? | Function arguments evaluate before the call; macro arguments evaluate only where the expansion places them. |
| What does it receive? | A function receives runtime values; a macro receives source forms represented as data. |
| What does it return? | A function returns a runtime value; a macro returns code to compile or evaluate. |
| How do you debug it? | Debug functions through values and stack traces; debug macros by inspecting expansion first. |
This difference is why macros can create syntax such as when, ->, and, or, let, and doseq.
Start with something familiar:
1(defmacro my-when
2 [condition & body]
3 `(if ~condition
4 (do ~@body)))
This macro does not “run the body.” It returns code that may run the body later. Expand it:
1(macroexpand-1
2 '(my-when (pos? total)
3 (println "positive")
4 :ok))
5
6;; roughly:
7(if (pos? total)
8 (do
9 (println "positive")
10 :ok))
Read the expansion as if a teammate generated that code in a pull request. If the expansion is hard to explain, the macro is already too clever.
Macros are not Java reflection. Reflection discovers and invokes members at runtime. Macros rewrite source forms before ordinary evaluation.
| Java-adjacent idea | Clojure macro distinction |
|---|---|
| Annotation processor | Both can generate code before runtime, but a Clojure macro is called directly in Clojure source. |
| Reflection | Reflection is runtime inspection; macro expansion is compile/load-time transformation. |
| Lombok-style boilerplate removal | Similar motivation, but macro expansion is visible with macroexpand-1. |
| Builder pattern | Usually still a function/data problem unless syntax or evaluation control matters. |
Before accepting a macro, verify:
| Check | Good sign |
|---|---|
| Expansion readability | The generated code is ordinary and reviewable. |
| Evaluation control | The macro has a reason a function cannot satisfy. |
| Argument evaluation | Each caller expression runs the intended number of times. |
| Name hygiene | Temporary names cannot capture or collide with caller names. |
| Tests | Tests cover both normal results and at least one expansion shape. |