exupero's blog

Simple macros

I don't write a lot of macros. For the small apps and improvisational code I use Clojure for, there isn't a lot of repetition to streamline by adding syntax. Every now and then I do tinker a little, so here are a few small macros I've collected over time.

The first is cond-let, which nests calls to if-let by calling itself recursively:

(defmacro cond-let [nm & clauses]
  (when (seq clauses)
    `(if-let [~nm ~(first clauses)]
       ~(second clauses)
       (cond-let ~nm ~@(drop 2 clauses)))))

Unlike if-let, cond-let doesn't use a vector as a binding form but instead assigns the result of each clause to the name given as the macro's first argument:

(let [word "beta"]
  (cond-let result
    (some #{\x \y \z} word)
    , (str "xyz: " result)
    (some #{\a \b \c} word)
    , (str "abc: " result)))
"abc: b"

If you want a more sophisticated version of cond, check out Mark Engelberg's better-cond.

Here's a macro that evaluates the values of a hash-map in parallel:

(defmacro parallel-hash-map [m]
  (let [ks (vec (keys m))
        vs (vals m)]
    `(zipmap ~ks (pvalues ~@vs))))

Note the call to vec. The list of map keys has to be converted to a vector, otherwise it expands into a function invocation.

I've used this macro to gather the results of several HTTP calls without having each call run serially:

    {:a (do (Thread/sleep 100) 1)
     :b (do (Thread/sleep 100) 2)}))
"Elapsed time: 105.225917 msecs"
{:a 1, :b 2}

ECMAScript 6 allows words in object literals to serve as both a key and a value, enabling expressions such as {a, b} to be a shorthand for {a: a, b: b}. We can macro something similar in Clojure:

(defmacro kv-map [& symbols]
     ~(mapv keyword symbols)
     ~(vec symbols)))
(let [a 1
      b 2]
  (kv-map a b))
{:a 1, :b 2}

ES6 also allows mixing the above syntax with traditional object field/value pairs, e.g. {a: 1, b}, but that's tricky to replicate in a Clojure macro without introducing additional delimiters and some level of parsing.

If you have additional macros, feel free to email me.