We’ve seen that pattern matching allows Elixir to decide which function to invoke based on the arguments passed. But what if we need to distinguish based on the argument types or on some test involving their values? For this, use guard clauses. These are predicates that are attached to a function definition using one or more when keywords. When doing pattern matching, Elixir first does the conventional parameter-based match and then evaluates any when predicates, executing the function only if at least one predicate is true.
| defmodule Guard do |
| def what_is(x) when is_number(x) do |
| IO.puts "#{x} is a number" |
| end |
| def what_is(x) when is_list(x) do |
| IO.puts "#{inspect(x)} is a list" |
| end |
| def what_is(x) when is_atom(x) do |
| IO.puts "#{x} is an atom" |
| end |
| end |
| |
| Guard.what_is(99) # => 99 is a number |
| Guard.what_is(:cat) # => cat is an atom |
| Guard.what_is([1,2,3]) # => [1,2,3] is a list |
Recall our factorial example:
| defmodule Factorial do |
| def of(0), do: 1 |
| def of(n), do: n * of(n-1) |
| end |
If we were to pass it a negative number, it would loop forever—no matter how many times you decrement n, it will never be zero. So it is a good idea to add a guard clause to stop this from happening.
| defmodule Factorial do |
| def of(0), do: 1 |
| def of(n) when is_integer(n) and n > 0 do |
| n * of(n-1) |
| end |
| end |
If you run this with a negative argument, none of the functions will match:
| iex> c "factorial2.exs" |
| [Factorial] |
| iex> Factorial.of -100 |
| ** (FunctionClauseError) no function clause matching in Factorial.of/1... |
Notice we’ve also added a type constraint: the parameter must be an integer.
You can write only a subset of Elixir expressions in guard clauses. The following list comes from the Getting Started guide.[8]
==, !=, ===, !==, >, <, <=, >=
or, and, not, !. Note that || and && are not allowed.
+, -, *, /
<> and ++, as long as the left side is a literal
Membership in a collection or range
These built-in Erlang functions return true if their argument is a given type. You can find their documentation online.[9]
is_atom | is_binary | is_bitstring | is_boolean | is_exception | is_float |
is_function | is_integer | is_list | is_map | is_number | is_pid |
is_port | is_record | is_reference | is_tuple |
These built-in functions return values (not true or false). Their documentation is online, on the same page as the type-check functions.
abs(number) | bit_size(bitstring) | byte_size(bitstring) | div(number,number) |
elem(tuple, n) | float(term) | hd(list) | length(list) |
node() | node(pid|ref|port) | rem(number,number) | round(number) |
self() | tl(list) | trunc(number) | tuple_size(tuple) |