Yes and no. From JavaScript you may be familiar with destructuring assignment. While useful it has one drawback - when the “shape” of the source value doesn’t “fit” the target structure, the parts in the target structure are simply left undefined
.
Pattern matching is often used for destructuring BUT if the “shape” of the source value doesn’t fit the pattern (or more simply the values don’t match) the match fails.
iex(1)> {a,b} = 3
** (MatchError) no match of right hand side value: 3
(stdlib) erl_eval.erl:453: :erl_eval.expr/5
(iex) lib/iex/evaluator.ex:249: IEx.Evaluator.handle_eval/5
(iex) lib/iex/evaluator.ex:229: IEx.Evaluator.do_eval/3
(iex) lib/iex/evaluator.ex:207: IEx.Evaluator.eval/3
(iex) lib/iex/evaluator.ex:94: IEx.Evaluator.loop/1
(iex) lib/iex/evaluator.ex:24: IEx.Evaluator.init/4
iex(1)> {a,b} = {2,4}
{2, 4}
iex(2)>
On that second try the match succeeded (so the value that is returned, is the value that matched; and yes coming from mainstream languages that looks like an assignment but in fact =
is the match operator - always (in that context). And a result of a successful match is that the names in the pattern are bound to their respective values, i.e. there are no variables, nothing varies).
So pattern matching is a conditional construct.
def choice(n) do
case({rem(n,3),rem(n,5), n}) do
{0, 0, _} -> "FizzBuzz" # 1. divisible by 3 and 5
{0, _, _} -> "Fizz" # 2. divisible by 3
{_, 0, _} -> "Buzz" # 3. divisible by 5
{_, _, x} -> "#{x}" # 4. just a number
end
end
def demo do
result = Enum.map(1..11, &choice/1)
IO.puts("#{inspect(result)}")
end
So what is happening in that case expression is that a tuple is created and then a pattern match is performed and whatever pattern matches first selects the clause that is evaluated.
def choice_tuple({0, 0, _}),
# 1. divisible by 3 and 5
do: "FizzBuzz"
def choice_tuple({0, _, _}),
# 2. divisible by 3
do: "Fizz"
def choice_tuple({_, 0, _}),
# 3. divisible by 5
do: "Buzz"
def choice_tuple({_, _, x}),
# 4. just a number
do: "#{x}"
def choice(n),
do: choice_tuple({rem(n, 3), rem(n, 5), n})
def demo do
result = Enum.map(1..11, &choice/1)
IO.puts("#{inspect(result)}")
end
… does exactly the same thing but the pattern match is spread over 4 separate function clauses (belonging to the same function choice_tuple/1
)
def choice(0, 0, _),
# 1. divisible by 3 and 5
do: "FizzBuzz"
def choice(0, _, _),
# 2. divisible by 3
do: "Fizz"
def choice(_, 0, _),
# 3. divisible by 5
do: "Buzz"
def choice(_, _, x),
# 4. just a number
do: "#{x}"
def choice(n),
do: choice(rem(n, 3), rem(n, 5), n)
def demo do
result = Enum.map(1..11, &choice/1)
IO.puts("#{inspect(result)}")
end
… gets rid of the tuple and spreads the values over 3 arguments. It’s still all pattern matching. Note that we have two separate choice
functions here: choice/1
(with an arity of one) and choice/3
(with an arity of three).