Part I. The language · this page as markdown

Tests and choices

A toy that says the same thing every time is a start. One that answers differently at night, or to a friend, or on the tenth ring, needs to choose. This chapter is about tests, which answer true or false, and the forms that choose with them.

Tests #

A test is a form that answers true or false:

#choices-tests
(= 3 3)
(= "rose" "Rose")
(not= 3 4)
(< 2 5)
(>= 5 5)
(< 1 2 3)
(even? 4)
(zero? 0)
(pos? -1)
(nil? nil)
(empty? "")
It answers
true
false
true
true
true
true
true
true
false
true
true

= compares any two values, lists and maps too, and text by every character, so "rose" and "Rose" differ. (< 1 2 3) asks whether each number is less than the next. Words ending in ? are tests by custom.

and, or and not put tests together:

#choices-logic
(and (> 5 3) (< 5 10))
(or (= 1 2) (= 2 2))
(not (= 1 2))
It answers
true
true
true

What counts as true #

A choice doesn't need true exactly. Every value counts as true except two: false and nil. So 0, "" and [] count as true, and a word that answers nil when it finds nothing can be used as a test. and and or use this too: or answers the first value that counts as true, and and the last, or the first that doesn't.

#choices-truthy
(or nil "a fallback")
(or (get {:rings 4} :chimes) 0)
(and "rose" "sage")
(and nil "sage")
It answers
"a fallback"
0
"sage"
nil

(or (get m :k) 0) is a common way to say "the value, or 0 if there isn't one".

if #

if takes a test, a value for when it holds, and a value for when it doesn't:

#choices-if
(if (> 12 10) "a dozen or more" "fewer")
(let [rings 1]
  (str "Rung " rings (if (= rings 1) " time" " times")))
It answers
"a dozen or more"
"Rung 1 time"

Inside a value, the third part can be left out, and the if answers nil when the test fails. At the top of a plan, an if that does something needs all three parts; when there is nothing to do otherwise, use when.

when #

when takes a test and any number of forms, and runs them only when the test holds:

#choices-when
(when (> 5 3) "yes")
(when (> 3 5) "no")
(str "a" (when false "b"))
It answers
"yes"
"a"
Nothing at home changed.

The second when did nothing at all, so it answered nothing. Inside a value, where something must be answered, a when whose test fails answers nil, which str writes as nothing: so the third answers "a".

when-not is when the other way round. if-let and when-let name a value and choose by it at once: the name is given only when the value counts as true.

#choices-when-let
(when-not (= 1 2) "they differ")
(if-let [guest (first ["@mira"])] (str "Hello, " guest) "Nobody yet")
(if-let [guest (first [])] (str "Hello, " guest) "Nobody yet")
It answers
"they differ"
"Hello, @mira"
"Nobody yet"

cond #

When there are more than two ways to go, cond takes tests and values in pairs and answers the value of the first test that holds. :else at the end is a test that always holds:

#choices-cond
(let [rings 12]
  (cond (= rings 0) "silent"
        (< rings 10) "warming up"
        (< rings 100) "well rung"
        :else "a famous bell"))
It answers
"well rung"

The tests are tried in order, so put the narrowest first.

case #

case compares one value with constants written out (numbers, text, keywords), and answers the value beside the one that's equal. A list of constants matches any of them, and a last value on its own is what to answer otherwise:

#choices-case
(case :hazy
  :sunny "Bright out."
  (:hazy :rain) "A soft grey day."
  "Some weather.")
(case 7 1 "one" 2 "two" "many")
It answers
"A soft grey day."
"many"

condp is case with a test of your choice: (condp = x 1 "one" 2 "two" "many").

Threading #

Choices often come at the end of a chain of small changes. -> passes a value through forms, each time as the first thing the next word takes, and ->> as the last. cond-> applies a step only when its test holds:

#choices-threading
(-> {:rings 0} (assoc :rings 3) (update :rings inc))
(->> [3 1 2] (sort) (map inc))
(cond-> {:rings 3} true (update :rings inc) false (assoc :broken true))
It answers
{:rings 4}
(2 3 4)
{:rings 4}

(-> {:rings 0} (assoc :rings 3) (update :rings inc)) is (update (assoc {:rings 0} :rings 3) :rings inc), written in the order it happens.

Try it #

  1. Write a cond that answers "cold", "mild" or "warm" for a temperature below 10, below 20, or otherwise.
  2. Use case to answer a greeting for :dawn, :day, :dusk and :night.
  3. What does (or nil false) answer? And (and 1 2 3)?
  4. With if-let, answer the first even number in [1 3 4 5] doubled, or "none".

Recap #

Next: working over a whole list at once.

Written by Claude, Plazmi's writer, for Plazmi, a service of Manatee Media LLC. Text © 2026 Manatee Media LLC. The examples are free to copy, change and run.