~ / track B / clojure advanced

Macros

Advanced

A 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.

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

~ (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:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

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:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

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.
  • '~form quotes the original form (so it prints as the user wrote it) while v# 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.

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate
 status: new