Clojure Macro Cheat Sheet

If you want to learn how to write Clojure macros, I recommend reading the macros chapter of Clojure for the Brave and True.

But for when you just want to remember the key parts of writing Clojure macros, here’s a handy cheat sheet.

Table of Contents

Quick Summary

  • Syntax Quote: ` (grave accent)
    • Usage: prefix to form
    • Purpose: Start a quoted form; kinda like starting a code “template”
  • Unquote: ~ (tilde)
    • Usage: prefix to form
    • Purpose: Within a syntax-quoted form, stop quoting, evaluate code instead
  • Splicing unquote: ~@
    • Usage: prefix to form
    • Purpose: Unquote which also unwraps a sequence returned from evaluation
  • Auto-gensym: # (hash, pound)
    • Usage: postfix to symbol
    • Purpose: Use on all local symbols created inside a syntax quoted form; prevents variable capture
  • Gensym: gensym
    • Usage: Fn; call on quoted symbol
    • Purpose: Use for all local symbols you want to create when syntax quoting is not currently in effect
  • Macro expansion: macroexpand-1
    • Usage: Fn; call on quoted form
    • Purpose: Debugging; view effect of macro on a (quoted) form

Most of these in an example:

(defmacro ignore-ex [ex-class & body]
  `(try
     ~@body
     (catch ~ex-class caught-ex#
       (printf
        "I don't care! Exception: %s\n"
        caught-ex#))))

(ignore-ex ArithmeticException
  (/ 1 0))
;;=> Expands to the equivalent of
(try
  (/ 1 0)
  (catch ArithmeticException caught-ex
    (printf
     "I don't care! Exception: %s"
     caught-ex)))

Tools Overview

Debugging: macroexpand-1 & eval

The main tool of your trade for debugging macros is the macroexpand-1 function.

Remember that you have to pass it a quoted form:

(macroexpand-1
 '(when true
    (println "foo")
    (println "bar")))
;;=>
(if true
  (do (println "foo")
      (println "bar")))

This returns an unevaluated form; use eval to evaluate it:

(eval *1)
;;=>
foo
bar

Quoting & unquoting

Create unevaluated forms with `, which you can inject evaluated forms into using ~.

(defmacro nums-to-add []
  `(+ 1 ~(inc 3)))

(macroexpand-1 '(nums-to-add))
;; =>
(clojure.core/+ 1 4)

(eval *1) ;=> 5

Splicing Unquote

Use ~@ to unquote a form which, when evaluated, becomes a sequence that you then want to unwrap.

The most common use-case for this is including an arbitrary number of “body” forms:

(defmacro dooo [& body]
  `(do (do (do ~@body))))

(macroexpand-1 '(dooo (println "foo")
                      (println "bar")))
;;=>
(do (do (do (println "foo")
            (println "bar"))))

(eval *1)
;;=>
foo
bar

Forgetting to splice

Here’s what happens when you forget to use ~@, and just do a normal unquote ~ instead:

(defmacro dooo-no-splice [& body]
  ;; NB only using ~ instead of ~@ here!
  `(do (do (do ~body))))

(macroexpand-1
 '(dooo-no-splice (println "foo")
                  (println "bar")))

;;=> NB note the unwanted
;; extra wrapping parens!
(do (do (do ((println "foo")
             (println "bar")))))

(eval *1)
;;=>
foo
bar
Execution error (NullPointerException)
at scratch.core/eval23583 (REPL:326).
Cannot invoke
"clojure.lang.IFn.invoke(Object)"
because the return value of
"clojure.lang.IFn.invoke(Object)"
is null

Auto-gensym

Whenever you need to create a local variable binding within syntax-quoted code, you need to gensym it:

(defmacro ignore-ex [ex-class & body]
  `(try
     ~@body
     (catch ~ex-class caught-ex#
       (printf
        "I don't care! Exception: %s\n"
        caught-ex#))))

(macroexpand-1
 '(ignore-ex ArithmeticException
    (/ 1 0)))
;;=>
(try
  (/ 1 0)
  (catch ArithmeticException
         caught-ex__23712__auto__
    (clojure.core/printf
     "I don't care! Exception: %s\n"
     caught-ex__23712__auto__)))

(eval *1)
;;=>
; I don't care! Exception:
; java.lang.ArithmeticException:
; Divide by zero

Forgetting to use auto-gensym

If you forget to use a gensym, you’ll generally get some sort of compilation error. This is a good thing though, because it’s better than having unnoticed variable capture.

(defmacro ignore-ex-no-gensym [ex-class & body]
  `(try
     ~@body
     (catch ~ex-class caught-ex
       (printf
        "I don't care! Exception: %s\n"
        caught-ex))))

(ignore-ex-no-gensym ArithmeticException
  (/ 1 0))
;;=>
; Syntax error compiling try
; Can't bind qualified name:
; scratch.core/caught-ex

Manual Gensym

When you need to generate a symbol within an unquoted expression, you need to call the gensym explicitly:

(defmacro with-log-context
  "Set SLF4J MC logging context
  key-value pairs, clearing them
  from the logging context when
  the form exits."
  [bindings & body]
  `(with-open
    ~(->> (partition 2 bindings)
          (mapcat
           (fn [[k v]]
             [(gensym '_)
              `(org.slf4j.MDC/putCloseable
                ~k
                (str ~v))]))
          vec)
     ~@body))

(macroexpand-1
 '(with-log-context ["foo" "bar"
                     "baz" "qux"]
    (log/info "hello!")))
;;=>
(clojure.core/with-open
 [_23618
  (org.slf4j.MDC/putCloseable
   "foo"
   (clojure.core/str "bar"))
  _23619
  (org.slf4j.MDC/putCloseable
   "baz"
   (clojure.core/str "qux"))]
  (log/info "hello!"))

;; i.e.
(with-open
  [_x (MDC/putCloseable "foo"
                        (str "bar"))
   _y (MDC/putCloseable "baz"
                        (str "qux"))]
  (log/info "hello!"))

Note also in the above example that we’re using nested syntax quoting. (Insert Inception reference here…)

Further Macro Examples

Other examples for inspiration and to help show the above tools in context:

Succinct Stubbing

You might want to create a succinct way of stubbing very commonly redefined functions in your tests:

(defn widget-fetch-fn []
  :foo)

(defmacro stub-widget
  [widget & body]
  `(with-redefs
     [widget-fetch-fn (constantly ~widget)]
     ~@body))

(stub-widget :bar
  (widget-fetch-fn))
;;=> :bar

(macroexpand-1
 '(stub-widget :bar
    (widget-fetch-fn)))
;;=>
(clojure.core/with-redefs
 [scratch.core/widget-fetch-fn
  (clojure.core/constantly :bar)]
  (widget-fetch-fn))

;; i.e.
(with-redefs
 [widget-fetch-fn (constantly :bar)]
  (widget-fetch-fn))

Testing that a spec exception has been thrown

(defmacro fails-with-spec-error
  [& body]
  `(try
     ~@body
     (is false
         (str "Shouldn't get here; "
              "expecting an exception"))
     (catch clojure.lang.ExceptionInfo ex#
       (is (= :instrument
              (:clojure.spec.alpha/failure
               (ex-data ex#)))))))

(macroexpand-1
 '(fails-with-spec-error
   (fn-which-should-trigger-error)))
;;=>
(try
  (fn-which-should-trigger-error)
  (clojure.test/is
   false
   (clojure.core/str
    "Shouldn't get here; "
    "expecting an exception"))
  (catch
   clojure.lang.ExceptionInfo
   ex__23627__auto__
    (clojure.test/is
     (clojure.core/=
      :instrument
      (:clojure.spec.alpha/failure
       (clojure.core/ex-data
        ex__23627__auto__))))))

;; i.e.
(try
  (fn-which-should-trigger-error)
  (is false
      (str "Shouldn't get here; "
           "expecting an exception"))
  (catch clojure.lang.ExceptionInfo ex
    (is (= :instrument
           (:clojure.spec.alpha/failure
            (ex-data ex))))))