Learn a practical first macro workflow in Clojure: choose a problem that truly needs compile-time transformation, write the expansion, inspect it with macroexpand, and keep the generated code boring.
Macro expansion: The process where a macro receives a source form and returns a new form for the compiler to continue processing.
The best first macro is not the cleverest one. It is the smallest one that demonstrates why a function would be insufficient. That usually means:
For learning purposes, an unless macro is still a good example even though real code would usually just use when-not.
Before writing defmacro, decide what ordinary form you want the caller’s code to become.
Desired source:
1(unless (pos? balance)
2 (println "Balance is zero or negative"))
Desired expansion:
1(if (not (pos? balance))
2 (do (println "Balance is zero or negative")))
Once the expansion is clear, the macro is mostly a form-construction problem.
1(defmacro unless [test & body]
2 `(if (not ~test)
3 (do ~@body)))
Important parts:
defmacro defines a macro rather than a functiontest receives the original test form, not its runtime result& body captures the remaining forms~ inserts one value~@ splices a sequence of forms into the outputThat is enough for a solid first macro.
Macro writing gets easier the moment you stop guessing about the emitted code.
1(macroexpand-1
2 '(unless (pos? balance)
3 (println "Balance is zero or negative")))
If the expansion is wrong, fix the macro before thinking about runtime behavior. Macro debugging usually starts at expansion time, not at execution time.
One of the healthiest macro habits is to prefer expansions into small core forms:
ifletdoloopThe macro itself can feel powerful, but the output should still be unsurprising. If the expansion looks like a compiler puzzle, the abstraction is probably too ambitious for a first macro.
The classic beginner macro bug is evaluating an expression more than once. If the input form has side effects or is expensive, duplicate evaluation changes semantics.
That is why larger macros often introduce temporary bindings using generated symbols. You do not need that in every first example, but you do need the habit of checking for it.
The caller should mostly experience:
If users of the macro constantly need to think about expansion internals, the abstraction has probably leaked too much.
That adds phase complexity without real gain.
Macro bugs are much easier to catch before runtime.
Generated forms should usually be simpler than the abstraction that produced them.
One input form may accidentally run multiple times unless the macro binds it carefully.
Choose a tiny problem that genuinely needs syntax transformation, write down the desired expansion first, implement the macro with syntax-quote, and inspect it with macroexpand-1 immediately. Keep the output boring, and be suspicious of any macro that evaluates a form more than once without meaning to. In Clojure, the best first macro is one whose expansion you can explain line by line.