Lesser known Clojure: variants of threading macro


The threading macros (-> and ->>) provide easy to understand and read mechanism of sequential transformation of data in Clojure. I’m pretty sure that you know it, but for the sake of clarity I start this post with a recap.

Thread first ->

The thread first -> macro allows pass the result of the first expression to the next one, that can be passed to another one and so. Previous result is always inserted as the second element of the form. If you think in term of function call, then the previous result is always the first parameter of the next function call. In other words, it’s a pipeline of functions invocation. If this is still not clear for you, let me show you an example:

(-> 5
    (- 2)
    (* 2)
    (> 1))

=> true

In this example we start with 5 that is passed to the (- 2) form resulting in (- 5 2) expression. The result (3) is passed to (* 2) which gives 6 and then the comparison is happening (> 6 1). So the final result is true.

We can also use macroexpand-1 to see how this code is transformed by the reader:

(macroexpand-1
 '(-> 5
      (- 2)
      (* 2)
      (> 1)))

here is result:

(> (* (- 5 2) 2) 1)

As you can see the thread first macro allows us to read (and write) code from top to bottom instead from the inside out (which usually is harder to read for humans)

Thread last ->>

The thread last ->> macro works almost the same as the -> form. The only difference is that result of the previous call is inserted as the last element of the form (last argument of the function call). This makes that thread last macro is ideal for working with functions operating on collections like: map, filter, reduce and so on, because those functions take collection as the last parameter. I will show you this on an example. Let’s say we have a vector with 5 numbers, we want to multiply every number by 3, then remove numbers that are lower then 10 and as the last step we want to sum them. To do it, we can write such code:

(->> [1 6 3 7 0]
     (map (fn [x] (* 3 x)))
     (filter (fn [x] (> x 10)))
     (reduce +))

=> 39

as you can see this makes code nice to read and reason about. As a confirmation of my words, let’s look how this code looks when we transform it with macroexpand-1:

(macroexpand-1 '(->> [1 6 3 7 0]
                     (map (fn [x] (* 3 x)))
                     (filter (fn [x] (> x 10)))
                     (reduce +)))

the result is:

(reduce +
        (filter (fn [x] (> x 10))
                (map (fn [x] (* 3 x))
                     [1 6 3 7 0])))

I think you agree that the code with ->> looks much better.

In version 1.5 of Clojure additional functions were added that extend the main idea of -> and ->> macros.

cond->

Let’s start with quoting a documentation:

(cond-> [expr & clauses])

Takes an expression and a set of test/form pairs. Threads expr (via ->)
through each form for which the corresponding test
expression is true. Note that, unlike cond branching, cond-> threading does
not short circuit after the first true test expression.

In other words, this forms allows us to thread previous result only to the next form if its condition is true. Let’s look at such sample:

(defn true-or-false []
  (= 1 (rand-int 2)))

(cond-> 1
  true (- 2)
  false (* 5)
  (true-or-false) (* 2))

1 is passed to (- 2) because its condition is true, then (* 5) is skipped (false) and the last expression (* 2) is executed only if true-or-false function returns true. The final result can be -1 or -2. There are two important things here:

  • the test part can’t use threaded value
  • if test is false, then the whole expression isn’t aborted, but the next test is evaluated.

For curiosity we can also see how this macro looks after expansion:

(clojure.core/let
 [G__15268 1
  G__15268 (if true (clojure.core/-> G__15268 (- 2)) G__15268)
  G__15268 (if false (clojure.core/-> G__15268 (* 5)) G__15268)
  G__15268(if (true-or-false) (clojure.core/-> G__15268 (* 2)) G__15268)]
 G__15268)

so this is basically a let form with if forms in in. The G__15268 is auto generated symbol (gensym function generates such names).

cond->>

The cond->> is conditional variant of ->> form. Same as that form, previous result is inserted as the last element of the next form. Again, it makes ideal fit for working with functions operating on collections.

Here you can see an example of its usage:

(defn process-collection [col use-map? use-filter? sum?]
  (cond->> col
    use-map? (map (fn [x] (* x 5)))
    use-filter? (filter (fn [x] (< x 25)))
    sum? (reduce +)))

We can use clojure.walk/macroexpand-all to expand this function - result are very similar to expanding example with cond->:

(def  process-collection
  (fn* ([col use-map? use-filter? sum?]
        (let*
         [G__15189 col
          G__15189 (if use-map? (map (fn* ([x] (* x 5))) G__15189) G__15189)
          G__15189 (if use-filter? (filter (fn* ([x] (< x 25))) G__15189) G__15189)
          G__15189 (if sum? (reduce + G__15189) G__15189)]
         G__15189))))

some->

As before, first let’s look at the documentation:

(some-> [expr & forms])
When expr is not nil, threads it into the first form (via ->),
and when that result is not nil, through the next etc

this means that some-> allows us threading result of previous expressions as long as none of them evaluate to nil. If one of the results is nil then the whole evaluation is stopped and nil is returned as some-> form result. If every result is not nil then some-> behaves as -> form.

The example:

(defn return-nil [x]
  nil)

(some-> 5
        inc
        (/ 3)
        return-nil
        (* 5))

in this case some-> returns nil because return-nil function returns nil

If we expand this macro, we get such code:

(clojure.core/let
 [G__15166 5
  G__15166 (if (clojure.core/nil? G__15166)
             nil (clojure.core/-> G__15166 inc))
  G__15166 (if (clojure.core/nil? G__15166)
             nil (clojure.core/-> G__15166 (/ 3)))
  G__15166 (if (clojure.core/nil? G__15166)
             nil (clojure.core/-> G__15166 return-nil))
  G__15166 (if (clojure.core/nil? G__15166)
             nil (clojure.core/-> G__15166 (* 5)))]
  G__15166)

Again, nothing special here, it is expanded to the let form with the if expressions.

some->>

I think now it’s obvious what some->> does. So let’s look only at usage example and it’s expansion:

(some->> [1 2 3]
         (map (partial * 6))
         return-nil
         (map odd?))

and it’s transformed into such code:

(clojure.core/let
 [G__15166 [1 2 3]
  G__15166 (if (clojure.core/nil? G__15166)
             nil (clojure.core/->> G__15166 (map (partial * 6))))
  G__15166 (if (clojure.core/nil? G__15166)
             nil (clojure.core/->> G__15166 return-nil))
  G__15166 (if (clojure.core/nil? G__15166)
             nil (clojure.core/->> G__15166 (map odd?)))]
  G__15166)

as->

The last macro we focus on is as->, its doc string says:

(as-> [expr name & forms])
Binds name to expr, evaluates the first form in the lexical context
of that binding, then binds name to that result, repeating for each
successive form, returning the result of the last form.

It took me a while before I fully understand how to us this form. The macro provides possibility to bind a given name to results of successive forms. I can find one good use case for this form (if you have more then please let me know in comments) - you have set of functions and you want to pass some data throughout those functions, but their signature differs (the data should be placed in different arguments locations). I think example will better explain this:

(defn f1 [_ n _ _]
  (inc n))

(defn f2 [n _ _]
  (dec n))

(defn f3 [_ _ _ n]
  (+ 5 n))

(as-> 1 n
  (f1 nil n nil nil)
  (f2 n nil nil)
  (f3 nil nil nil n))

=> 6

In this example we want to pass value 1 (that is bind to symbol n) to function f1, then the result should be passed to f2 and this result to f3. As you can see those functions have different signatures and we need to pass n on different positions.

After expanding form with as-> we get such code:

(clojure.core/let
    [n 1
     n (f1 nil n nil nil)
     n (f2 n nil nil)
     n (f3 nil nil nil n)]
  n)

which is easy to understand (it uses only let form).

I hope you enjoy this overview of threading macros available in Clojure.


You may also like

ClojureScript: JavaScript Interop

Template method pattern

Lesser known Clojure: max-key and min-key