Published on
Clojure's cond-> (and cond->>) is a versatile macro. It isn't a new macro, it has been around since version 1.5, but I finally discovered and started using it sometime last year. It isn't a workhorse macro, you won't be using it everyday, but it comes in handy.
cond->?Let's start by looking at the docstring.
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.
So what does the docstring mean? Let's break it down with an example.
(cond-> 10
false inc)
=> 10
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. In this example 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.
(cond-> 10
true (- 2)
=> 8
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.
Next is 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?
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.
(if (some-pred? q)
(assoc m :a-key :a-value)
m)
You can rewrite the above with cond->.
(cond-> m
(some-pred? q) (assoc :a-key :a-value))
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.