In JavaScript, = assigns: it takes a value on the right and stores it in a
variable on the left. In Elixir, the same symbol means something else
entirely. = is a match: it asks whether the two sides agree, and the
variables on the left get bound to whatever makes them agree. Read = this
way and Elixir code becomes easier to follow. It also explains the recursion
exercises on this site, where base cases and recursive steps use patterns
instead of if statements.
The match, not the assignment
x = 5
It looks like assignment. It is a match: the left side, a variable, matches
anything, so the match succeeds and binds x to 5. That is why it looks
like assignment. A lone variable matches anything, so the general match
machinery acts like assignment in this one case. The difference appears when
the left side is not a bare variable:
{1, x} = {1, 2} # succeeds: x = 2
{1, x} = {2, 2} # fails: 1 does not match 2
The left side is a pattern: a shape with holes. The match checks whether
the right side has that shape, then binds the holes. This is how the language
makes decisions. Where JavaScript writes if (a === 1) { ... }, Elixir
matches the value against the pattern 1. Where JavaScript writes const x = arr[0], Elixir matches the list against [head | tail] and gets both the first element and the rest in one step.
Clauses are ordered dispatch
The recursion exercises’ solutions are two lines:
def sum([]), do: 0
def sum([head | tail]), do: head + sum(tail)
Each def is a clause, and each clause’s arguments are patterns. When you
call sum([1, 2, 3]), Elixir tries the clauses in order: does [1, 2, 3]
match []? No. Does it match [head | tail]? Yes - head = 1, tail = [2, 3] - so that clause runs. A call to sum([]) matches the first clause
immediately and never reaches the second.
Order matters. Put the base case first because the first matching clause
wins. If you put the recursive clause first, sum([]) would match it, try to
peel a head off the empty list, and crash. Clause order is the function’s
logic: check the base case before the step, just as an induction proof states
the base case before the inductive step. That is why the site’s recursion
exercises insist on “base case first”: pattern matching enforces the rule.
Destructuring: one match, several bindings
The pattern [head | tail] does three things in one step: it checks that the
list is non-empty, binds head to the first element, and binds tail to the
rest. The same idea works with nested shapes:
{name, {age, city}} = {"ana", {29, "lisbon"}}
One match pulls the whole structure apart and names every piece. The
recursion exercises use this constantly. member/2 needs the head to
compare and the tail to recurse, and one pattern gives it both:
def member([x | _tail], x), do: true
def member([_head | tail], x), do: member(tail, x)
The second clause does something a variable alone cannot: it uses the same
variable name twice. [x | _tail], x matches only when the head and the
second argument are the same value. In an assignment language you would
write a check. Here, the pattern is the check. The underscore names -
_tail, _head - are the pattern for “some value I do not need”.
The two halves of the recursion skeleton
Pattern matching makes the base-case/step skeleton of recursion literal. The
base case is a pattern for the smallest input ([]), the step is a pattern
for the general shape ([head | tail]), and Elixir dispatches between them by
matching. There is no if anywhere in sum/1; the pattern makes the
decision. The induction article’s base case and inductive step have the same
relationship: the base pattern, the general pattern, and the guarantee that
every input matches exactly one of them.
The pattern [head | tail] also carries the descent. Matching it gives you
the tail, and recursing on the tail is what guarantees termination - the same
smaller-input descent the recursion article describes. Pattern matching and
recursion are not two ideas that happen to coexist in Elixir. The language
was built so that the shape of the data and the shape of the code are the
same shape.
Why this matters here
The recursion write exercises are pattern matching lessons wearing a recursion costume: write the base clause, write the step clause, put the base first. The induction bridge exercises show the same structure from the proof side. And the reading-elixir-as-a-js-developer article - the on-ramp that gets you from JavaScript to this site’s Elixir -
-
ends where this article begins: the
=sign is not what you thought, and every Elixir function you read from now on is a set of patterns waiting to match.
Where to go next
-
Stacks, queues, and associative arrays
- the site’s own head-of-the-list skill in its natural habitat: pulling the top off a stack by pattern.
-
Reading Elixir as a JS developer
-
the on-ramp this article’s closing mentions: the
=you have been reading wrong, from the JavaScript side.
-
the on-ramp this article’s closing mentions: the