Erlang from the ground up

Function heads and guards

One function name can have several heads. Erlang picks the first one whose pattern matches and whose guards are true, which is how most decisions are really made.

Part 4 of 12

Beginner16 min readBack to Courses

One name, several heads

A function is written as a series of clauses, each with its own head: the name, the arguments as patterns, and then an arrow. The clauses are separated by semicolons and the whole group finishes with a full stop. grade(0) -> none. followed by more clauses is one function with several ways in.

Erlang reads the clauses from top to bottom and takes the first whose head matches the call. The last clause is usually a catch-all written with underscore or a plain name, so that some clause always applies. A call that matches none of them raises a function clause error, which is a real answer rather than silence.

Because the head is a pattern, the choice can be about shape rather than value: one clause for the empty list, one for a list with something in it, one for a tuple tagged ok, one for a tuple tagged error. The function then reads as a table of cases.

Guards narrow the choice

A pattern can only ask about shape and fixed values. To compare numbers, add a guard after the keyword when: grade(Score) when Score >= 90 -> excellent; is one clause that matches any score above 89. Guards can use comparisons, arithmetic, and a small set of built-in tests such as is_integer and is_list.

A clause whose pattern matches but whose guard is false is skipped, and Erlang tries the next clause. That is why guards are the natural way to write a ladder of ranges: each clause states its band, and the clauses are ordered so that the bands do not overlap.

Why this shape is worth learning

Clause selection and guards are the same machinery the rest of the language uses. case is a set of clauses applied to one value; if is a set of clauses with guards and no pattern. Learning to read the head of a function is learning to read all of them at once.

One habit pays off immediately: put the most specific heads first and the catch-all last. A catch-all at the top would swallow every call and the clauses after it would never run.

Three functions, eight clauses

-module(prog).
-export([main/1]).

main(_) ->
    io:format("~p~n", [grade(95)]),
    io:format("~p~n", [grade(75)]),
    io:format("~p~n", [grade(40)]),
    io:format("~p~n", [sign(-3)]).

grade(Score) when Score >= 90 -> excellent;
grade(Score) when Score >= 70 -> good;
grade(_) -> try_again.

sign(0) -> zero;
sign(N) when N > 0 -> positive;
sign(_) -> negative.

A ladder of clauses

Run it and check each answer against the bands in the code. Then add a clause for scores below 20 and call it from the top of the list.

Output
Click "Run" to execute your code

If a function seems to ignore its second clause, check the order. Clauses are tried from the top, so a catch-all placed first wins every call and the clauses below it are dead code that never runs.

Guards are a small language of their own

A guard is not free-form code. It may use comparisons, arithmetic, atoms, and a fixed set of built-in tests such as is_integer, is_list and length, but it may not call a function you wrote. The restriction exists so a guard can always answer quickly and cannot itself fail in an unexpected way.

Conditions combine with a comma for and and a semicolon for or: when N >= 13, N < 65 is one band, while when N < 0; N > 100 accepts either extreme. andalso and orelse also work and stop evaluating as soon as the answer is known, which matters when the second test would fail on the first test’s value.

The clauses of one function belong together

All the clauses of a function must sit next to each other and agree on the name and the arity. Semicolons separate the clauses and the final full stop ends the group. A full stop in the middle looks finished to the compiler, so the next clause is read as a new function with a different arity, and the call you expected fails.

The head decides shape and the guard decides values, and both are tried before the body runs. That is why a guard can mention a name bound in the head, and why a clause whose guard is false is skipped without any side effect.

Three guard mistakes cost real time. Calling a local function inside a guard is a compile error, because guards may only use built-in tests. A comma means and and a semicolon means or, so swapping them silently changes every band. And a catch-all clause placed above a guarded clause hides it forever, because the first matching head wins.

Try these next

  • Add a teen band for ages 13 to 19 and call it with 13, 19 and 20.
  • Write one clause guarded by is_integer(N) and a fallback for anything else.
  • Rewrite when N >= 13, N < 65 with andalso and confirm the answers do not change.

Prices chosen by age and day

Run it and check each price against the clause that produced it. Then add a `student` price for weekdays and call it.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

Two clause heads both match a call. Which one runs?