To explore how macros work to solve real problems, let’s
work up an alternative implementation of a commonly used macro in Clojure:
->, which is very similar to its
->>. Often called
threading macros, these are included in
clojure.core (with a number of derivatives with
similar semantics in various third-party libraries), and are remarkably
useful in cleaning up chained function calls and chained Java-interop
What we’d like to do is be able rewrite somewhat awkward code like this:
(prn (conj (reverse [1 2 3]) 4))
…and instead write code like this:
(thread [1 2 3] reverse (conj 4) prn)
This way, rather than reading code inside-out (which can be
difficult with deeply nested calls), we can read our code sequentially,
left-to-right as a series of successive actions: “Start with [1 2 3],
conj 4 onto it, then
It’s not hard to envision a macro to do this. Given a series of forms, we’ll take the first form and insert it as the second item in the second form, then take the resulting form and insert it as the second item in the third form, and so on.
Additionally, if any form after the first is not already a list, let’s consider it a list of one item. This lets us avoid parens on single-argument functions like this:
(-> foo (bar) (baz))
and instead write:
(-> foo bar baz)
First, let’s write a simple utility function to ensure that a form is a seq.
(defn ensure-seq [x] (if (seq? x) x (list x))) (ensure-seq 'x) ;= (x) (ensure-seq '(x)) ;= ...