Elixir pattern matching - save your time, similar with the way of our thinking
M岷h V农
Posted on July 8, 2024
Intro
Once of interesting features of Elixir is pattern matching. That is way to save your time and similar with the way of our thinking.
Pattern matching help us to check value, extract value from complex term, branching code.
It also is a way to fail fast, follow "Let it crash" (and supervisor will fix) idiomatic of Erlang.
How to uses
Pattern matching is always return original term (value of variable/expression) for us can continue using it for other expression.
Three things we need to care in here:
- If does not match it will raise an error.
- Pattern matching is always return original term.
- If we add a variable for extracting, after matched variable will have value we expected.
With '=', pattern is in the left side, original term in right side.
We can use many patterns in an expression like:
{:ok, result} = {:ok, {total, list}} = {:ok, {1, ["hi"]}}
or
{:ok, {total, list} = result} = {:ok, {1, ["hi"]}}
With function, pattern is placed in argument.
No we go to two use cases for using pattern matching.
check similar(condition) value
This type of pattern matching can help us verify a value, format of variable/param.
Term is matched with pattern ({:ok, "hello"} = {:ok, "hello"}):
(check value and return original term)
Term is unmatched with pattern ({:ok, "abc"} = {:ok, "hello"}):
(term is unmatched with pattern, raise an runtime error)
For case using pattern in argument of function, if pattern is unmatched with argument, Elixir will check with next function. If no function is matched with the pattern, an runtime error will be raised.
Simple, we can check value of variables like:
a = :an_atom
b = 1_000
c = {:ok, 1}
# ...
# use case 1
:an_atom = a
1_000 = b
{:ok, 1} = c
# use case 2
case a do
:an_atom ->
:ok
_other ->
:nok
end
case b do
1_000 ->
:default
_ ->
:user_config
end
# use case 3
check_ok = fn
:ok -> true
_ -> false
end
result = check_ok.(a)
For case check equal value, that usually uses for verifying value, condition for branch code in function/case do.
If variable is complex term example a tuple, list, map. We can discard other values to check one or more values we want.
Example:
list = [1, :a, "hello"]
tuple = {:ok, :a, 1}
map = %{a: 1, b: "hello"}
# check first item of list is 1.
[1|_] = list # or ^list = [1|_]
# check tuple has same values.
copy = ^tuple = {:ok, :a, 1}
# check tuple has same number of elements.
{_, _, _} = tuple
# check map has a key = :a and value of key = 1.
%{a: 1} = map
Extracting value
This feature of pattern matching help us can extract one or more value from a complex term.
Remember, our expected value will bind to variable follow format of our pattern or key in map case.
(expression of pattern matching:
{:ok, get_value} = {:ok, "hello"}.
after matched, we got "hello" value from original term)
Example:
tuple = {:ok, "hello"}
list = [:a, :b, 1, 2]
complex = {:ok, [{:first, 1}, {:second, [1, 2, 3]}]}
# extract second element of tuple
{:ok, value} = tuple
# get first item in list
[firt|_] = list
# get second item in list
[_, second|_] = list
# get value of result
{:ok, [{_, first_value}, {:second, second_value}]} = complex
We can use all key of example for function like:
defmodule Example do
def process_second([_, second|_]) do
# do something with second item.
IO.puts("Value of second item: #{inspect second}")
second
end
def extract_map(%{a: value} = map) do
# do some thing with value & map.
IO.puts("key a has value = #{inspect value}")
value
end
end
# use cases for pattern matching in function arguments.
list = [1, :default, "hello"]
map = %{a: "hello", b: "world"}
Example.process_second(list)
Example.extract_map(map)
Other common cases for pattern matching with head of function are separating code like:
defmodule Exam do
def process_result({:error, reason}) do
# do something with error case.
end
def process_result({:ok, result}) do
# do something with result.
end
end
For multi patterns in one expression we can declare like:
list = {:ok, {:waiting, [1, 2, 3]}, {:running, [4, 5, 6]}}
{:ok, _, _} = {_, {:waiting, list1}, _} = {_, _, {:running, list2}} = list
# after run, list1 = [1, 2, 3], list2 = [4, 5, 6]
Pattern matching with Binary/Bitstring
This is interesting way to work with binary in Elixir. I wish other languages have this feature.
We have two type of raw data is binary (for work with byte) and bitstring (for work with bit).
Elixir has very convenient way to work with raw data like binary and bitstring. We can construct and matching these easily.
In this topic we just go to how to match a binary or bitstring only.
Binary matching
Example:
# each byte is an integer 8 bit.
bin = <<1, 2, 3, 4, 5>>
# Get first byte
<<first, rest::binary>> = bin
# Get second byte
<<1, second, rest::binary>> = bin
# get 2 bytes in head of binary
<<head::binary-size(2), rest::binary>> = bin
Is it simple right?
Bitstring matching
We can match & get single bit from a raw data (bitstring).
Example:
bit = <<3::4, 1::4, 3::4, 0::4>>
# get first 8 bits from bitstring.
<<first_4::bitstring-size(8), rest::bitstring>> = bit
# get 4 bits after first 4 bits is match to <<3::4>> and store to get_4_bits variable
<<3::4, get_4_bits::bitstring-size(4), _>> = bit
We can easy to process raw data with pattern matching.
Posted on July 8, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.