Mix.install([
{:youtube, github: "brooklinjazz/youtube"},
{:hidden_cell, github: "brooklinjazz/hidden_cell"},
{:tested_cell, github: "brooklinjazz/tested_cell"},
{:utils, path: "#{__DIR__}/../utils"}
])
Ensure you type the ea
keyboard shortcut to evaluate all Elixir cells before starting. Alternatively you can evaluate the Elixir cells as you read.
We've already used pattern matching for matching values in a data structure.
[_, two, _] = [1, 2, 3]
two
We can use pattern matching in a wide variety of other cases.
Previously, we talked about how the case statement checks for a match.
case "same" do
"same" -> "this will return"
end
We're also able to use pattern matching for case statements. The first valid match will execute, and you can use the variables you assign in the instruction.
case [1, 2] do
[] -> nil
[one] -> one
[one, two] -> one + two
end
Trigger the "A"
case. Replace nil
with your answer.
case nil do
[_, _, 3] -> "A"
_ -> "default"
end
You create multi-clause functions with pattern matching.
defmodule Greeter do
def greet([name1, name2]) do
"Hello, #{name1} and #{name2}"
end
def greet(%{name: name, identity: identity}) do
"Hi #{identity} err..I mean #{name}"
end
def greet(name) do
"Hello, #{name}"
end
end
The function that first pattern matches with the provided input will be executed.
Greeter.greet("Peter")
Greeter.greet(["Peter", "Bruce"])
Greeter.greet(%{name: "Batman", identity: "Bruce Wayne"})
Use pattern matching to create a Hello.everyone/1
function which only accepts a list with three or more elements.
Example solution
defmodule Hello do
def everyone([_, _, _ | _tail]) do
"Hello, everyone!"
end
end
Enter your solution below.
defmodule Hello do
def everyone(list) do
end
end
We can use pattern matching in anonymous functions.
hello = fn
"Peter" -> "Hey Spidey!"
name -> "Hello, #{name}."
end
hello.("Bruce")
hello.("Peter")
Pattern matching becomes seriously powerful when leveraged with the Enum module.
list = [1, 2, 3]
Enum.map(list, fn
1 -> "one"
int -> int * 2
end)
Now we can enumerate and handle different patterns of data separately.
list = [{:add, 4}, {:subtract, 2}, {:multiply, 2}, 10, %{add: 2}]
Enum.reduce(list, 0, fn
{:add, int}, acc -> acc + int
{:subtract, int}, acc -> acc - int
{:multiply, int}, acc -> acc * int
%{add: int}, acc -> acc + int
int, acc -> acc + int
end)
Use Enum.map/2 with pattern matching and multi-clause functions to double {:double, integer}
tuples and half {:halve, integer}
tuples in the following list.
[{:double, 2}, {:halve, 10}, {:double, 4}] -> [4, 5, 8]
Example Solution
Enum.map([{:double, 2}, {:halve, 10}, {:double, 4}], fn
{:double, integer} -> integer * 2
{:halve, integer} -> div(integer, 2)
end)
[{:double, 2}, {:halve, 10}, {:double, 4}]
Often we have many tools to accomplish the same action. For example, let's say we're building an application where users send each other messages. However, only admin users are allowed to send messages.
Using if
, we could write the following.
defmodule Message do
def send(user, message) do
if user.is_admin do
message
else
{:error, :not_authorized}
end
end
end
Let's say we also need to handle empty messages.
defmodule Message do
def send(user, message) do
if user.is_admin do
if message == "" do
{:error, :empty_message}
else
message
end
else
{:error, :not_authorized}
end
end
end
Message.send(%{is_admin: true}, "")
Nested if
statements are generally a cue that we should consider an alternative implementation.
Let's see how we could solve this problem with pattern matching.
defmodule Message do
def send(%{is_admin: true}, "") do
{:error, :empty_message}
end
def send(%{is_admin: true}, message) do
{:ok, message}
end
def send(%{is_admin: false}, _) do
{:error, :not_authorized}
end
end
Message.send(%{is_admin: true}, "")
Message.send(%{is_admin: false}, "Error!")
Message.send(%{is_admin: true}, "Successful!")
Pattern matching can help reduce the complexity of our control flow.
Anytime that we can use multiple function clauses, we can also use a case
statement.
defmodule Message do
def send(user, message) do
case {user, message} do
{%{is_admin: true}, ""} -> {:error, :empty_message}
{%{is_admin: true}, message} -> message
{%{is_admin: false}, _} -> {:error, :not_authorized}
end
end
end
The pin operator allows us to use variables as hard-coded values, rather than rebinding a variable.
Often we use the pin operator when testing our code to assert that the value is correct.
For example, the following will rebind the received variable to [1, 2, 3]
.
received = [1, 2]
expected = [1, 2, 3]
received = expected
But instead, we might use the match operator to check that the received value matches the expected value.
received = [1, 2]
expected = [1, 2, 3]
^received = expected
By using the pin operator above, we accomplish the same as if we had written:
[1, 2] = [1, 2, 3]
We can also use this for internal values in a collection. The following is the same
as [1, 2, 3] = [2, 2, 3]
first = 1
actual = [2, 2, 3]
[^first, 2, 3] = actual
And the following is the same as [1, 2, 3] = [1, 2, 3]
first = 1
actual = [1, 2, 3]
[^first, 2, 3] = actual
Use the pin operator to make the following code crash with a MatchError because expected
does not match actual
, rather than rebinding expected
as it is currently doing.
Example Solution
expected = {"hello"}
actual = {"hello", "hi"}
^expected = actual
expected = {"hello"}
actual = {"hello", "hi"}
expected = actual
Consider the following resource(s) to deepen your understanding of the topic.
- Elixir Schools: Pattern Matching
- Exercism.io: Pattern Matching
- elixir-lang.org: Pattern Matching
- HexDocs: Patterns and Guards
Run the following in your command line from the beta_curriculum folder to track and save your progress in a Git commit.
$ git add .
$ git commit -m "finish advanced pattern matching section"
Previous | Next |
---|---|
Polymorphism | Treasure Matching |