Pattern matching: the equals sign that matches
The equals sign does not assign, it matches. How to bind a name, unpack a shape you already know, and pin a name to the value it holds.
Equals means match
In most languages X = 42 stores 42 in X. In Erlang it is a claim: the left side and the right side are the same value. If X is not yet bound, the claim succeeds and X takes the value. If X is already 42, the claim succeeds again and nothing changes.
Everything follows from that. A pattern is a shape with holes in it, and matching a value against a pattern either succeeds and fills the holes, or fails. A failed match in a function raises an error, which is why the patterns in a program are also a form of checking.
A bound name on the left is compared, not overwritten, so X = 42 after X = 42 is fine while X = 43 fails. There is no way to change a value in Erlang; you can only make a new name.
Unpacking a known shape
Matching is how a program takes data apart. {ok, Value} = {ok, 7} binds Value to 7, and the same line with {error, Reason} on the right would fail, because the first elements of the two tuples differ. For a list, [Head | Tail] = [1, 2, 3] binds Head to 1 and Tail to [2, 3].
A hole can be marked as uninteresting with an underscore: {_, Second} = {a, b} takes the second element and says nothing about the first. An underscore never binds anything, so it can appear many times in one pattern.
A name that keeps its value
Because a bound name in a pattern means equality, an existing name can be used as a test. After Pinned = 5, the pattern Pinned matches only the value 5. That is how Erlang holds onto something and asks whether a later value is the same one.
This is the whole of pattern matching, and it is used everywhere later: in function heads, in case, in the list patterns of recursion. The equals sign is not storage. It is a question with a yes or no answer.
Binding, unpacking, and testing
-module(prog).
-export([main/1]).
main(_) ->
X = 42,
io:format("~p~n", [X]),
X = 42,
{ok, Value} = {ok, 7},
io:format("~p~n", [Value]),
[Head | Tail] = [1, 2, 3],
io:format("~p ~p~n", [Head, Tail]),
{_, Second} = {a, b},
io:format("~p~n", [Second]),
Pinned = 5,
case 5 of
Pinned -> io:format("the same one~n");
_ -> io:format("a different one~n")
end.Take a value apart
Run it and follow what each pattern claims. Then change `X = 42` on the seventh line to `X = 43` and see what the run says.
The pattern {ok, Value} does not check that something went well; it insists. If the value is {error, disk_full} the program stops with a bad match instead of carrying on, so match only on shapes you are sure of, or use case when the shape may vary.
A pattern is read from the outside in
A pattern describes the shape of a value, and matching works from the outside in. {order, Ref, [First | Rest]} first demands a three-element tuple, then demands that the third element is a non-empty list, then binds the pieces. If any layer disagrees, the whole match fails; there is no partial success to inspect.
That is why a nested pattern is both a way to unpack data and a way to state an assumption. The clause only runs when the shape is exactly what the pattern says, and the names inside it are already bound by the time the body starts.
Why a value never changes
There is no assignment in Erlang, so a name is bound once and keeps that value for the rest of the function. A later expression that looks like a change, such as X = X + 1, is a match against the new value: it fails, because the right side is no longer equal to the bound X.
This is not a restriction to work around; it is what lets a function hand a value to another process without copying or locking it, and what makes a match a reliable test. When the value really should change, the program makes a new name, often with a suffix such as Next or Rest, and passes it along.
When a match fails, the error names the value that did not fit: {badmatch, {error, disk_full}}. That is more useful than a line number, because the offending value is printed in full. Read it from the inside out and compare it with the pattern on the left before changing the code.
Three matching mistakes are easy to make. Writing X = 43 after X is bound does not assign, it fails. Matching a specific tag such as {ok, Value} on a value that may be an error crashes instead of branching. And a name beginning with an underscore, such as _Count, still binds a value; only the bare _ is the throwaway pattern.
Try these next
- Match the first two elements of a four-element list with
[A, B | _]and print both. - Take a nested tuple apart in one pattern, then rebuild a similar tuple from the bound names.
- After binding
N = 4, tryN = 5and read the bad match message before removing the line.
Unpack a nested order
Run it and follow every name back to the value it took. Then add a second order and match its first two items with a list pattern.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
After X = 42, what happens on X = 42 again?
