Guards
Pattern matching can check the shape of a value. Guards add a second
layer: they check the content. A guard is a short expression that
starts with the keyword when and must evaluate to true for the
match to succeed.
Here is the smallest guard example:
iex> defmodule Check do
...> def number?(n) when is_integer(n), do: "yes, an integer"
...> def number?(_), do: "no"
...> end
iex> Check.number?(42)
"yes, an integer"
iex> Check.number?("42")
"no"
The first clause only runs when is_integer/1 returns true. For any
other value the guard fails and Elixir falls through to the next
clause.
Where Guards May Appear
Guards are allowed in every place that does pattern matching:
# In a function clause
def can_vote?(age) when is_integer(age) and age >= 18, do: true
# In a case expression
case value do
n when is_number(n) and n > 0 -> "positive number"
_ -> "something else"
end
# In an anonymous function
classify = fn
n when is_integer(n) -> :integer
n when is_float(n) -> :float
_ -> :other
end
# In for and with
for n <- list, is_integer(n), do: n * 2
cond does not take guards directly because its branches are
already arbitrary boolean expressions.
|
Common Guard Functions
Only a limited set of functions may appear inside a guard. The table below lists the ones you will use most often as a beginner. The full list is at https://hexdocs.pm/elixir/patterns-and-guards.html.
| Function | Purpose |
|---|---|
|
true if the value is an atom |
|
true if the value is a binary (a string is a binary) |
|
true if the value is a bitstring |
|
true if the value is |
|
true if the value is a float |
|
true if the value is a function |
|
true if the value is a function of the given arity |
|
true if the value is an integer |
|
true if the value is a list |
|
true if the value is a map |
|
true if the map has the given key |
|
true if the value is |
|
true if the value is an integer or float |
|
true if the value is a process identifier |
|
true if the value is a reference |
|
true if the value is a tuple |
| Function | Purpose |
|---|---|
|
number of bytes in a binary |
|
number of elements in a tuple |
|
number of entries in a map |
|
number of elements in a list (walks the whole list) |
|
head of a list |
|
tail of a list |
|
element at a given index of a tuple |
| Operator | Purpose |
|---|---|
|
equality |
|
comparison |
|
arithmetic |
|
strict boolean logic |
|
membership in a list or range |
Here is a combined example that uses several of these:
iex> defmodule Temperature do
...> def describe(t) when is_number(t) and t < 0, do: "freezing"
...> def describe(t) when is_number(t) and t in 0..20, do: "cool"
...> def describe(t) when is_number(t) and t > 20 and t <= 30, do: "warm"
...> def describe(t) when is_number(t) and t > 30, do: "hot"
...> def describe(_), do: "not a number"
...> end
iex> Temperature.describe(-5)
"freezing"
iex> Temperature.describe(25)
"warm"
iex> Temperature.describe("warm")
"not a number"
What You Cannot Do in a Guard
Guards must stay small and side-effect free. That rules out:
-
your own functions (only the built-in guards above are allowed),
-
calls that could raise or have side effects (like
String.length/1on a non-binary), -
if,case,condand similar control structures.
If you need something more complex, move the check into the body of
the clause and branch with case or cond there:
iex> defmodule Example do
...> def handle(value) when is_binary(value) do (1)
...> case String.length(value) do (2)
...> 0 -> "empty string"
...> n when n > 0 -> "string of length #{n}"
...> end
...> end
...>
...> def handle(_), do: "not a string"
...> end
iex> Example.handle("")
"empty string"
iex> Example.handle("hello")
"string of length 5"
iex> Example.handle(42)
"not a string"
| 1 | The guard only checks the shape. |
| 2 | The richer check happens inside the function body. |
You can write your own guard with defguard once you start
building libraries. For beginners, the built-in guards are plenty.
|