Browse Learn Clojure Foundations as a Java Developer

Use Macros for Clojure Metaprogramming

Learn how macros fit into Clojure metaprogramming, where they beat functions, and how to review the generated code before treating syntax as an abstraction.

Macros are one metaprogramming tool, not the whole story. Use them when you need to transform source forms into different source forms before evaluation.

The most useful mental model is:

1caller form -> macro expansion -> ordinary Clojure form -> runtime behavior

A Small Transformation

1(defmacro unless
2  [condition & body]
3  `(when-not ~condition
4     ~@body))

The macro receives the condition and body forms unevaluated. It returns a form that uses ordinary Clojure.

1(macroexpand-1
2  '(unless (:ready? job)
3     (println "not ready")
4     :wait))
5
6;; roughly:
7(clojure.core/when-not (:ready? job)
8  (println "not ready")
9  :wait)

Review the expansion as generated source. If the expansion is not readable, the macro is not ready.

Macro or Function?

Requirement Prefer
Transform values already computed at runtime Function
Accept caller behavior as a callback Higher-order function
Describe configuration Data
Delay, skip, or splice caller forms Macro
Create new binding/control syntax Macro

Common Metaprogramming Shapes

Shape Example Review focus
Control form unless, with-retry Evaluation order and body count.
Binding form with-open, with-resource Scope, cleanup, generated locals.
Definition generator defroutes, defevent Names, generated vars, error messages.
Small DSL Query or routing syntax Predictable expansion and limited vocabulary.

Java Translation

Java habit Clojure macro lesson
Annotation hides generated methods Inspect macro expansion before trusting call syntax.
Reflection adapts at runtime Use macros only when source shape must change.
Builder creates runtime configuration Prefer Clojure data unless syntax is the point.

Review Before Sharing

Before merging a macro:

Check Good answer
Why not a function? The macro controls syntax or evaluation.
What does one representative expansion look like? It is short and ordinary.
Are caller expressions evaluated the intended number of times? Yes, usually once.
Are generated locals hygienic? Yes, auto-gensyms or explicit gensyms.

Knowledge Check

### What does a macro do in Clojure metaprogramming? - [x] It transforms caller forms into generated forms before evaluation. - [ ] It runs after every function call. - [ ] It performs Java reflection automatically. - [ ] It converts all maps into classes. > **Explanation:** Macros receive forms and return forms. The returned form is what Clojure later analyzes and runs. ### When is a function the better metaprogramming alternative? - [x] When the abstraction only needs already evaluated values. - [ ] When caller forms must be spliced into generated code. - [ ] When a new binding form is required. - [ ] When macro expansion must happen. > **Explanation:** If ordinary values are enough, a function is simpler and easier to test. ### What should be reviewed before sharing a macro? - [x] A representative expansion and its evaluation behavior. - [ ] Only the macro's docstring. - [ ] The JVM class name generated by Clojure. - [ ] The number of namespaces in the project. > **Explanation:** The expansion reveals evaluation order, generated locals, and the real code shape.
Revised on Saturday, May 23, 2026