Does your macro provide & body
arguments? Continue reading.
1. try
splicing
Does your macro splice body
into a try
like so?
`(try ~@body (finally ...))
- Try passing
(catch Exception e)
as your body arguments. Your macro can catch exceptions. - Try passing
(finally)
as your body arguments. Your macro cannot invoke variables calledfinally
.
For example:
(defmacro foo [& body]
`(try ~@body (finally 1)))
(foo (finally 1))
;; Syntax error compiling try at (REPL:1:1).
;; finally clause must be last in try expression
2. fn
splicing
Does your macro splice body
into a fn
like so?
`(fn [] ~@body)
- Try passing
(recur)
as your body arguments. Your macro now runs forever. - Try passing
{:pre [false]} (inc 1)
as your body arguments. Your macro supports preconditions.
For example:
(defmacro bar [& body]
`((fn [] ~@body)))
(bar {:pre [false]} 1)
;; Execution error (AssertionError) at user/eval155$fn (REPL:1).
;; Assert failed: false
3. Composition of core macros
Does your macro expand to any of these macros?
- locking, binding, with-bindings, sync, with-local-vars, with-in-str, dosync, with-precision, with-loading-context, with-redefs, delay, lazy-seq, lazy-cat, future, pvalues
Does the call look like this?
`(<core-macro> ... ~@body)
You have inherited one or more of the above entertaining features.
For example:
(defmacro baz [& body]
`(locking 1
~@body))
(baz (finally 1))
;; Syntax error compiling try at (REPL:1:1).
;; finally clause must be last in try expression
How to fix it
Any time you have ~@body
, do this instead:
`(let [res# (do ~@body)] res#)
It forces ~@body
to always be in an expression position without a recur target.
If that’s too extreme, just wrap it in a (do ~@body)
to force expression position.