pattern_matching.md
February 2, 2014 ยท View on GitHub
Pattern Matching
Pattern matching lives at the heart of the Erlang Virtual Machine. When binding or invoking a function, the VM is pattern matching on the provided expression.
a = 1 # 1
1 = a # 1
b = 2 # 2
^a = b
** (MatchError) no match of right hand side value: 2
iex(5)> ^a = 1
1
iex(6)> [first, 2, last] = [1, 2, 3]
[1, 2, 3]
iex(7)> first
1
iex(8)> last
3
^a = b shows the syntax for pattern matching against a variable's value instead of performing assignment.
A common technique is to return a tuple from a function with the atom :ok, followed by the requested value. This allows the caller to pattern match against the return value and let the program crash when an unexpected error occurs:
iex(1)> {:ok, logger_pid} = DB.start_logger
If DB.start_logger failed to open the log file and returned {:error, :enoent}, the program would crash with a MatchError since pattern matching was performed solely by the use of the = operator. This is useful where programs can take no other sane action than crashing, such as when a database's logger fails to open.
Control Flow Pattern Matching
Pattern matching can be used with the case keyword for powerful control flow handling. case pattern matches against each clause and invokes the first match. At least one clause must be matched or a CaseClauseError is raised.
Let's write a mini calculation parser to perform a few basic operations:
iex(1)> calculate = fn expression ->
...(1)> case expression do
...(1)> {:+, num1, num2} -> num1 + num2
...(1)> {:-, num1, num2} -> num1 - num2
...(1)> {:*, num1, 0} -> 0
...(1)> {:*, num1, num2} -> num1 * num2
...(1)> end
...(1)> end
#Function<6.17052888 in :erl_eval.expr/5>
iex(2)> calculate.({:+, 8, 2})
10
iex(3)> calculate.({:-, 8, 2})
6
iex(4)> calculate.({:*, 8, 2})
16
iex(5)> calculate.({:^, 8, 2})
** (CaseClauseError) no case clause matching: {:^, 8, 2}
iex(5)>
Function Pattern Matching
Pattern matching is not just limited to bindings. Elixir uses pattern matching for anonymous and named function invocation as well.
Run it (./src/countdown.ex)
defmodule Countdown do
def run(from, to) when from >= to do
run(from, to, from)
end
def run(_from, to, current) when to == current do
IO.puts to
IO.puts "Done!"
end
def run(from, to, current) do
IO.puts current
run(from, to, current - 1)
end
end
iex(10)> c "countdown.ex"
[Countdown]
iex(11)> Countdown.run(10, 2)
10
9
8
7
6
5
4
3
2
Done!
:ok
iex(13)> Countdown.run(10, 11)
** (FunctionClauseError) no function clause matching in Countdown.run/2
countdown.ex:3: Countdown.run(10, 11)
Guard Clauses are used with the postfix when on function definitions. Multiple functions with the same name can be defined because the guard becomes part of the function's unique signature. Each definition is pattern matched against when calling run until the first match is found. Pattern matching on functions is run from top to bottom, in the order the functions are defined.
Record Pattern Matching
Run it (./src/pattern_match_records.exs)
defrecord Person, gender: nil, name: ""
defmodule PersonPrefixer do
def prefix(p = Person[gender: :male]), do: "Mr."
def prefix(p = Person[gender: :female]), do: "Mrs."
end
defmodule SharingMatchLogic do
defmacro is_male(p) do
ix = Person.__record__(:index, :gender)
quote do: elem(unquote(p), unquote(ix)) == :male
end
defmacro is_female(p) do
ix = Person.__record__(:index, :gender)
quote do: elem(unquote(p), unquote(ix)) == :female
end
def prefix(p = Person[]) when is_male(p), do: "Mr." # won't work
def prefix(p = Person[]) when is_female(p), do: "Mrs." # won't work
end
john = Person.new gender: :male, name: "John"
jane = Person.new gender: :female, name: "Jane"
PersonPrefixer.prefix(john) |> IO.puts
PersonPrefixer.prefix(jane) |> IO.puts
SharingMatchLogic.prefix(john) |> IO.puts
SharingMatchLogic.prefix(jane) |> IO.puts