Search code examples
clojure

Conditional elements in -> / ->> pipelines


Given a ->> pipeline like so:

(defn my-fn []
  (->> (get-data)
       (do-foo)
       (do-bar)
       (do-baz)))

I wish to make the various stages conditional.

The first way of writing this that came to mind was as such:

(defn my-fn [{:keys [foo bar baz]}]
  (->> (get-data)
       (if foo (do-foo) identity)
       (if bar (do-bar) identity)
       (if baz (do-baz) identity))

However, as the ->> macro attempts to insert into the if form, this not only looks unfortunate in terms of performance (having the noop identity calls), but actually fails to compile.

What would an appropriate, reasonably DRY way of writing this be?


Solution

  • Modern Clojure (that is, as of 1.5) supports a variety of options for conditional threading, but you probably want cond->>.

    conditional threading with cond-> and cond->>

    Clojure offers cond-> and cond->>, which each ask for a set of pairs: a test and an expression if that test evaluates to true. These are quite similar to cond but don't stop at the first true test.

    (cond->> 5
      true inc
      false inc
      nil inc)
    => 6
    

    Your specific example is probably best written like so:

    (defn my-fn [{:keys [foo bar baz]}]
      (cond->> (get-data)
        foo (do-foo)
        bar (do-bar)
        baz (do-baz)))
    

    as->

    It's worth mentioning as-> because it is perhaps the most versatile threading macro. It gives you a name to refer to the "thing" being threaded through the forms. For instance:

    (as-> 0 n
      (inc n)
      (if false
        (inc n)
        n))
    => 1
    
    (as-> 0 n
      (inc n)
      (if true
        (inc n)
        n))
    => 2
    

    This gives substantial flexibility when working with a mix of functions that require threading the expression at different points in the parameter list (that is, switching from -> to ->> syntax). One should avoid the use of extraneous named variables in the interest of code readability, but oftentimes this is the clearest and simplest way to express a process.