Decisions: case, cond and guards
Three ways to choose a path — case on the shape of a value, guards for a condition a pattern cannot express, and cond for a ladder of questions.
case: one path per shape
A case compares a value against patterns, top to bottom, and the first pattern that fits wins. Because the patterns are matches, they also name the parts you need: {:ok, temp} hands the branch a temp to work with. The patterns are tried in the order written, so a specific pattern belongs above a general one that would also fit.
Each branch is a pattern, an arrow, and the answer for that case. The branch you land in decides the value of the whole case, so you can hand a case straight to a function or print it. There is no fall-through and no break — one pattern, one path. The value the case answers is the value of the branch that ran, so it can sit inside a string interpolation or be handed to a function without a temporary name.
Guards: when the shape is not enough
Sometimes a pattern fits but you want more: a temperature above 25, a number that is really a number. A guard does that with when after the pattern, as in {:ok, temp} when temp > 10 ->. If the guard fails, the case moves on to the next clause as though the pattern had not fitted at all. That is the important difference from an if inside the body: a failed guard moves to the next clause, so guards are how a case chooses between several versions of the same shape.
Guards also sit on function heads, which is how a module can offer one function name that behaves differently for different inputs. Only a small, safe set of checks is allowed inside a guard — comparisons, arithmetic, and the is_ checks such as is_integer and is_atom. The restriction is deliberate: a guard can never run your code, so it can never fail in a surprising way. The compiler enforces the list, so a call to String.length in a guard is rejected before the program runs.
cond, if, and what counts as true
A cond is a ladder of conditions rather than a match on a shape. Each line is a condition and an answer, and the first condition that is true wins. The last rung is usually just true, which catches everything the earlier rungs missed. Use cond when several different questions have to be asked in order, and case when one value has several possible shapes. A cond can ask about different values on each rung, which is exactly what a case cannot do: a case has one subject and matches every clause against it.
For a single yes-or-no question there is if, and its mirror unless. In Elixir only false and nil count as false — every other value is true, including 0 and the empty string. That is a smaller rule than most languages have, and it means a test that looks for emptiness has to say what it means.
A guard, a case, and a cond
defmodule Grade do
def label(score) when score >= 90, do: :great
def label(_score), do: :keep_going
end
IO.inspect(Grade.label(95))
IO.inspect(Grade.label(70))
reading = {:ok, 12}
case reading do
{:ok, temp} when temp > 10 -> IO.puts("warm: #{temp}")
{:ok, temp} -> IO.puts("cold: #{temp}")
{:error, reason} -> IO.puts("failed: #{reason}")
end
band = fn n ->
cond do
n > 100 -> :big
n > 10 -> :medium
true -> :small
end
end
IO.inspect(band.(500))
IO.inspect(band.(50))A weather reading, judged three ways
Run it and read the four reports in order. Then add a reading of 30 degrees, change one number so the middle guard is the one that fires, and give the if at the bottom a false condition.
A case with no matching clause raises and the program stops. Either cover every shape the value can have, or finish with a catch-all pattern such as other -> so an unexpected value becomes something you can see in the output rather than a crash.
What a guard may do
A guard is deliberately poorer than a function body. It may compare, do arithmetic, and call a small set of is_ checks, but it may not call your code or a function such as String.length. The restriction is what makes a guard safe: the runtime can evaluate it without side effects and without the possibility of raising, so a failing guard simply moves on to the next clause.
cond asks its questions in order and stops at the first true one. If none is true it raises, which is why the last rung is usually the literal true. case is different: it matches shapes, and a guard can narrow a shape but cannot replace it.
A guard that calls String.length will not compile; compute the length before the case and guard on the number. A cond with no true condition raises CondClauseError, so keep a final true -> rung. And a case does not fall through: the first pattern that fits runs, and every clause below it is skipped.
Try these next
- Write a
casefor{:ok, n}and{:error, reason}that prints a different sentence for each. - Add a guard to a function head so it accepts only positive integers.
- Rewrite a two-branch
ifas acondwith a finaltruerung.
A ticket price by age
Run it and check each ticket against the age rules. Then add a band for people over 80, give one reading a negative age, and print what the guard does with it.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does when add to a pattern?
