Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditional elements in -> / ->> pipelines

Tags:

clojure

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?

like image 349
Charles Duffy Avatar asked Aug 01 '12 21:08

Charles Duffy


3 Answers

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.

like image 26
Dave Liepmann Avatar answered Oct 22 '22 21:10

Dave Liepmann


This works too:

(defn my-fn [{:keys [foo bar baz]}]
  (->> (get-data)
       (#(if foo (do-foo %) %))
       (#(if bar (do-bar %) %))
       (#(if baz (do-baz %) %)) ))
like image 51
Don Avatar answered Oct 22 '22 19:10

Don


You might be interested in these macros https://github.com/pallet/thread-expr

like image 9
Hugo Duncan Avatar answered Oct 22 '22 21:10

Hugo Duncan