# Tests and choices

From A Little Lisp for a Small World, the Plazmi Lisp book (https://plazmi.gg/book). Checked against Plazmi Lisp version 2, 12 November 2026. The whole book as one file: https://plazmi.gg/book.md

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`:

```clojure
(= 3 3)
(= "rose" "Rose")
(not= 3 4)
(< 2 5)
(>= 5 5)
(< 1 2 3)
(even? 4)
(zero? 0)
(pos? -1)
(nil? nil)
(empty? "")
```

```text
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:

```clojure
(and (> 5 3) (< 5 10))
(or (= 1 2) (= 2 2))
(not (= 1 2))
```

```text
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.

```clojure
(or nil "a fallback")
(or (get {:rings 4} :chimes) 0)
(and "rose" "sage")
(and nil "sage")
```

```text
"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:

```clojure
(if (> 12 10) "a dozen or more" "fewer")
(let [rings 1]
  (str "Rung " rings (if (= rings 1) " time" " times")))
```

```text
"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:

```clojure
(when (> 5 3) "yes")
(when (> 3 5) "no")
(str "a" (when false "b"))
```

```text
"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.

```clojure
(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")
```

```text
"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:

```clojure
(let [rings 12]
  (cond (= rings 0) "silent"
        (< rings 10) "warming up"
        (< rings 100) "well rung"
        :else "a famous bell"))
```

```text
"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:

```clojure
(case :hazy
  :sunny "Bright out."
  (:hazy :rain) "A soft grey day."
  "Some weather.")
(case 7 1 "one" 2 "two" "many")
```

```text
"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:

```clojure
(-> {: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))
```

```text
{: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

- Tests answer `true` or `false`: `=`, `not=`, `<`, `>`, `even?`, `nil?`, `empty?` and the like; `and`, `or` and `not` join them.
- Only `false` and `nil` count as false.
- `if` chooses between two; `when` does something or nothing; `cond` tries pairs in order; `case` compares with constants.
- At the top of a plan, an `if` needs both its branches; use `when` for one.

Next: working over a whole list at once.


---

Before: [Text, lists and maps](https://plazmi.gg/book/text-lists-maps.md). Next: [Working over lists](https://plazmi.gg/book/over-lists.md). Contents: https://plazmi.gg/book.md
