case, cond, and if

View Source

In this chapter, we will learn about the case, cond, and if control flow structures.

case

case allows us to compare a value against many patterns until we find a matching one:

iex> case {1, 2, 3} do
...>   {4, 5, 6} ->
...>     "This clause won't match"
...>   {1, x, 3} ->
...>     "This clause will match and bind x to 2 in this clause"
...>   _ ->
...>     "This clause would match any value"
...> end
"This clause will match and bind x to 2 in this clause"

If you want to pattern match against an existing variable, you need to use the ^ operator:

iex> x = 1
1
iex> case 10 do
...>   ^x -> "Won't match"
...>   _ -> "Will match"
...> end
"Will match"

Clauses also allow extra conditions to be specified via guards:

iex> case {1, 2, 3} do
...>   {1, x, 3} when x > 0 ->
...>     "Will match"
...>   _ ->
...>     "Would match, if guard condition were not satisfied"
...> end
"Will match"

The first clause above will only match when x is positive.

Keep in mind errors in guards do not leak but simply make the guard fail:

iex> hd(1)
** (ArgumentError) argument error
iex> case 1 do
...>   x when hd(x) -> "Won't match"
...>   x -> "Got #{x}"
...> end
"Got 1"

If none of the clauses match, an error is raised:

iex> case :ok do
...>   :error -> "Won't match"
...> end
** (CaseClauseError) no case clause matching: :ok

The documentation for the Kernel module lists all available guards in its sidebar. You can also consult the complete Patterns and Guards reference for in-depth documentation.

if

case builds on pattern matching and guards to destructure and match on certain conditions. However, patterns and guards are limited only to certain expressions which are optimized by the compiler. In many situations, you need to write conditions that go beyond what can be expressed with case. For those, if is a useful alternative:

iex> if true do
...>   "This works!"
...> end
"This works!"
iex> if false do
...>   "This will never be seen"
...> end
nil

If the condition given to if returns false or nil, the body given between do-end is not executed and instead it returns nil.

if also supports else blocks:

iex> if nil do
...>   "This won't be seen"
...> else
...>   "This will"
...> end
"This will"

Expressions

Some programming languages make a distinction about expressions (code that returns a value) and statements (code that returns no value). In Elixir, there are only expressions, no statements. Everything you write in Elixir language returns some value.

This property allows variables to be scoped to individual blocks of code such as if, case, where declarations or changes are only visible inside the block. A change can't leak to outer blocks, which makes code easier to follow and understand. For example:

iex> x = 1
1
iex> if true do
...>   x = x + 1
...> end
2
iex> x
1

You see the return value of the if expression as the resulting 2 here. To retain changes made within the if expression on the outer block you need to assign the returned value to a variable in the outer block.

iex> x = 1
1
iex> x =
...>   if true do
...>     x + 1
...>   else
...>     x
...>   end
2

With all expressions returning a value there's also no need for alternative constructs, such as ternary operators posing as an alternative to if. Elixir does include an inline notation for if and, as we will learn later, it is a syntactic variation on if's arguments.

if is a macro

An interesting note regarding if is that it is implemented as a macro in the language: it isn't a special language construct as it would be in many languages. You can check the documentation and its source for more information.

If you find yourself nesting several if blocks, you may want to consider using cond instead. Let's check it out.

cond

We have used case to find a matching clause from many patterns. We have used if to check for a single condition. If you need to check across several conditions and find the first one that does not evaluate to nil or false, cond is a useful construct:

iex> cond do
...>   2 + 2 == 5 ->
...>     "This will not be true"
...>   2 * 2 == 3 ->
...>     "Nor this"
...>   1 + 1 == 2 ->
...>     "But this will"
...> end
"But this will"

This is equivalent to else if clauses in many imperative languages - although used less frequently in Elixir.

If all of the conditions return nil or false, an error (CondClauseError) is raised. For this reason, it may be necessary to add a final condition, equal to true, which will always match:

iex> cond do
...>   2 + 2 == 5 ->
...>     "This is never true"
...>   2 * 2 == 3 ->
...>     "Nor this"
...>   true ->
...>     "This is always true (equivalent to else)"
...> end
"This is always true (equivalent to else)"

Similar to if, cond considers any value besides nil and false to be true:

iex> cond do
...>   hd([1, 2, 3]) ->
...>     "1 is considered as true"
...> end
"1 is considered as true"

Summing up

We have concluded the introduction to the most fundamental control-flow constructs in Elixir. Generally speaking, Elixir developers prefer pattern matching and guards, using case and function definitions (which we will explore in future chapters), as they are succinct and precise. When your logic cannot be outlined within patterns and guards, you may consider if, falling back to cond when there are several conditions to check.