Clojure cond->λ︎
cond->
and cond->>
are versatile macros available since version 1.5, although its more of a nieche use, its really useful in that neiche
Usage: (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.
Deconstructλ︎
In the above example 10 is the expr mentioned in the docstring and everything after it are the clauses.
Each clause is a pair made up of a test and a form. There is a single clause with the value false as the test the function inc as the form.
Since the test evaluates to a false value the expression is not threaded into the form. As a result the original expression, 10, is returned.
Let’s look at an example with a truthy test.
Once again, 10 is the starting expression. The single clause has a test that evaluates to true so the expression is threaded into the first position of the form (- 2). The result is 8 and this is returned.
An example of a cond->
with multiple clauses. Explanations are inline with the code.
(cond-> 10 ; start with 10
;; test evaluates to true, so apply inc to 10. Current value is now 11.
true inc
;; (zero? 1) evaluates to false, do not perform action. Current value stays 11.
(zero? 1) (+ 2)
;; (pos? 4) evaluates to true, thread 11 into first position of form.
(pos? 4) (- 5))
;; => 6 ; The result of (- 11 5) is 6.
If you understand the above example then you have a good grasp of cond->. But when is this functionality useful?
When to use cond->?λ︎
Looking through the codebases I work on, I almost primarily see cond-> being used with the initial expression being a hash-map. It is being used in situations where we want to selectively assoc, update, or dissoc something from a map.
If cond-> did not exist you would accomplish those selective modifications with code similar to below.
Rewrite the above with cond->.
If you’re not used to seeing cond-> the above transformation might seem like a step backwards. I know it felt that way to me when I first saw cond->. Give yourself time to get familiar with it and you’ll be glad you’re using it.
A meatier example of using cond-> is demonstrated below. Here we’re manipulating data structures designed for use with honeysql to generate SQL statements. We start with a base-query and selectively modify it based on incoming parameters.
(defn query [req-params]
(let [and-clause (fnil conj [:and])
base-query {:select [:name :job]
:from [:person]}]
(cond-> base-query
(:job req-params) (update :where and-clause [:= :job (:job req-params)])
(:name req-params) (update :where and-clause [:= :name (:name req-params)])
(:min-age req-params) (update :where and-clause [:> :age (:min-age req-params)]))))
Hopefully this gives you a taste of cond->. I’ve found it to be quite useful. It has a place in every Clojure developer’s toolbox.