Deciding what to do
if, when and cond, plus the truth rule that makes Clojure decisions easy to read: only nil and false mean no.
The truth rule is two values long
A decision needs a yes or a no, and Clojure keeps that short: nil and false are the only values that mean no. Zero means yes. An empty string means yes. An empty vector means yes. If you come from a language where zero counts as false, this is the one habit to change. The rule is the whole test: if does not inspect a type or apply a rule of its own, it asks only whether the value is nil or false.
That single rule is why Clojure code so rarely compares anything with true or false. A function that looks something up and hands you either a value or nil can be dropped straight into a test. A lookup that returns nil is already a no, and a lookup that returns any value at all is already a yes; nothing has to be compared with true.
if, and its two branches
(if test yes no) chooses between two expressions. The test is worked out once, exactly one branch runs, and the value of that branch is the value of the whole if. Leave the last branch off and a failing test answers nil, which is fine when there is nothing to say. Only the chosen branch is worked out, so an unchosen branch may hold an expression that would fail — a division by zero, or a lookup on a value that is not there.
(when test body ...) is the one-armed version, and it lets you write several expressions in the body without wrapping them up: each line runs, and the last one is the value you get. Reach for when there is only something to do, rather than a choice between two things.
cond, for more than two ways
(cond test-a result-a test-b result-b :else result) reads down the page and takes the first test that is true. The final :else is just a test that always wins, because a keyword is neither nil nor false, and it gives you somewhere to put the leftover case. Each test is worked out in order and the first true one wins, so a later test is never even looked at once an earlier one has matched.
Order matters in a cond: the first true test is the one that goes, so write the narrowest case first and let :else catch the rest.
Both branches are values
if is not a statement that does something; it is an expression that answers a value. That is why both branches have to be there when the result is used, and why an if with no else answers nil rather than doing nothing. Both branches answer a value, and the whole if is only as clear as that pair: a branch that answers a number and one that answers text makes the next line guess.
The same is true of cond: each pair is a test and the value to answer when that test wins. There is no falling through and no assignment inside a branch, only a value chosen. If a branch needs several steps, wrap them in a let or call a function that answers one value.
Tests can be any expression
A test can be any expression, not only a comparison. (:member record) is a test, (seq items) is a test, and (contains? tags :lisp) is a test. All three answer a value, and the two-value truth rule decides what it means.
That is why Clojure code rarely writes (= true x). A lookup that finds something is already true enough, and a lookup that misses gives nil, which is already false. The test says what it wants instead of comparing it to true.
One test, two branches, then a list of cases
(def age 36)
(println (if (odd? age) "odd" "even"))
(when (> age 30)
(println "over thirty"))
; Only nil and false are false.
(println (if 0 "zero is true" "zero is false"))
(println (if nil "yes" "no"))
(def votes {:ada 3 :alan 5})
(println
(cond
(> (:ada votes) (:alan votes)) "ada wins"
(< (:ada votes) (:alan votes)) "alan wins"
:else "a tie"))Three ways to choose
Run it and read the five lines. Then change the age to 15 and run it again, and add a cond case that says "exactly thirty" when the age is 30.
Printing the answer of a failed when shows nil on the pane, and that is information rather than a problem: nothing happened, and the value is the quiet nothing. Later you will meet if-let and when-let, which test a value and name it in the same line.
Three mistakes show up in first conds. A cond needs an even number of forms: a test with no value after it is a syntax error at the end of the form, not a branch that does nothing. Without :else, a cond whose tests all fail answers nil, which can look like a bug much later. And when is not if: it has no else branch, so do not try to give it one; use if when there are two outcomes.
Try these next
- Write an
ifthat treats 0 as true and prints which branch ran. - Turn a score into three
condbranches, then add a fourth for a score below zero. - Use
whento print a message only when a name is present, and check what the whole form answers when it is not.
A ticket price with three decisions
Run it and read the five lines. Then change the age to 70 and run it again, and add a `when` that prints a reminder only when the member flag is false.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
In Clojure, which values mean no in a test?
