Guard Clauses

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.

mm/guard.exs
 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:

mm/factorial1.exs
 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.

mm/factorial2.exs
 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.

Guard-Clause Limitations

You can write only a subset of Elixir expressions in guard clauses. The following list comes from the Getting Started guide.[8]

Comparison operators

==, !=, ===, !==, >, <, <=, >=

Boolean and negation operators

or, and, not, !. Note that || and && are not allowed.

Arithmetic operators

+, -, *, /

Join operators

<> and ++, as long as the left side is a literal

The in operator

Membership in a collection or range

Type-check functions

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

Other functions

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)