@Version : 4.5.0
@Build : 94d077c24
By using this site, you acknowledge that you have read and understand the Cookie Policy, Privacy Policy, and the Terms. Close

Elixir Pattern Matching Notes

Posted Thursday, March 26th, 2020

Elixir
Elixir Pattern Matching Notes

I have written this for my learning and for easy reference on key techniques that I want to keep close by. If you have been doing elixir, you most likely use pattern matching with everyday code that you write. But if you are new to elixir, master pattern matching and you will write code that will make sense to anyone long after you are gone writing code in alien languages. Its makes code simple and very readable. If it is there, grab it, whatever happens downstream, I choose to care or not.

If you find any mistakes or suggestion to learn together on, this link will help create an issue on GitHub and I will be happy to correct and learn together.

The Idea of Pattern Matching.

In elixir = is the match operator. Will compare both sides and assign left variable to right as value. This only works if both sides have same structure or assignment is possible.

iex(33)> 1=1
1
iex(34)> :ok=:ok
:ok
iex(35)> [1]=[1]
[1]
iex(36)> {:ok, result} = {:error, :oops}
** (MatchError) no match of right hand side value: {:error, :oops}

Now we look at matching data structures. Any data structure in Elixir can be pattern matched with different rules applied by the compiler. Knowing them will make life easy for you.

Matching Maps and Structs.

We can match atom key maps.

iex(1)> %{name: name, age: age} = %{name: "Danstan", age: 28}
%{age: 28, name: "Danstan"}
iex(2)> name
"Danstan"
iex(3)> age
28
iex(4)>

We can match string key maps.

iex(4)> %{"name" => name, "age" => age} = %{"name" => "Danstan", "age" => 28}
%{"age" => 28, "name" => "Danstan"}
iex(5)> age
28
iex(6)> name
"Danstan"
iex(7)>

Matching structs is the same as matching maps.

iex(39)> defmodule User do
...(39)>  defstruct name: "John", age: 27
...(39)> end
iex(40)> user = %User{name: "Danstan", age: 28}
%User{age: 28, name: "Danstan"}
iex(41)> %{name: name} = user
%User{age: 28, name: "Danstan"}
iex(42)> name
"Danstan"
iex(43)>

Matching Lists.

Match first item in the list and rest of items in another variable. This is useful for recursively iterating over a list of items.

iex(10)> [first | rest] = [1, 2, 3, 4]
[1, 2, 3, 4]
iex(11)> first
1
iex(12)> rest
[2, 3, 4]
iex(13)>

Match entire list. Useful for getting values of list items. This only works if you read all items on the other side or the number of items to match.

iex(13)> [ user_1, user_2] = [ %{name: "Danstan"}, %{name: "John"}]
[%{name: "Danstan"}, %{name: "John"}]
iex(14)> user_1
%{name: "Danstan"}
iex(15)> user_2
%{name: "John"}
iex(16)>

Matching Keyword Lists.

Match keyword lists only works when the number of items to match.

iex(17)> [ version: version ] = [ version: "4.5.0"]
[version: "4.5.0"]
iex(18)> version
"4.5.0"
iex(19)> [ version: version ] = [ version: "4.5.0", branch: "master"]
** (MatchError) no match of right hand side value: [version: "4.5.0", branch: "master"]
    (stdlib) erl_eval.erl:453: :erl_eval.expr/5
    (iex) lib/iex/evaluator.ex:257: IEx.Evaluator.handle_eval/5
    (iex) lib/iex/evaluator.ex:237: IEx.Evaluator.do_eval/3
    (iex) lib/iex/evaluator.ex:215: IEx.Evaluator.eval/3
    (iex) lib/iex/evaluator.ex:103: IEx.Evaluator.loop/1
    (iex) lib/iex/evaluator.ex:27: IEx.Evaluator.init/4
iex(19)>

Matching Binaries.

Matching binaries can be by length.

iex(24)> <<hello::binary-size(5), world::binary>> = "Hello World!"
"Hello World!"
iex(25)> hello
"Hello"
iex(26)> world
" World!"
iex(27)>

Another cute way is

iex(27)> "Hello" <> world = "Hello World!"
"Hello World!"
iex(28)> world
" World!"
iex(29)>

Matching Atoms and tuples

Matching atoms is probably the most common matching you do.

iex(44)> :error = :error
:error
iex(45)> {:error, message} = {:error, "something happened"}
{:error, "something happened"}
iex(46)> message
"something happened"
iex(47)> {one, two, three} = {1, 2, 3}
{1, 2, 3}
iex(48)> one
1
iex(49)> two
2
iex(50)> three
3
iex(51)>

This is used to do a lot of error handling in Elixir.

How to utilize pattern matching.

Error handling.

You will always use pattern matching to handle errors in Elixir. Its the holy elixir way and its fun. See..

iex(51)> case Map.fetch(%{a: 1}, :a) do
...(51)> {:ok, value} -> value
...(51)> :error -> {:error, "Map has no key :a"}
...(51)> end
1
iex(52)> case Map.fetch(%{a: 1}, :b) do
...(52)> {:ok, value} -> value
...(52)> :error -> {:error, "Map has no key :b"}
...(52)> end
{:error, "Map has no key :b"}
iex(53)>

Iteration over loops

iex(55)> defmodule NumberSystem do
...(55)>  def sqr_list([], sqrd), do: sqrd
...(55)>  def sqr_list([first | rest], sqrd) do
...(55)>   sqr_list(rest, sqrd ++ [first * first])
...(55)>  end
...(55)> end
iex(56)> NumberSystem.sqr_list([1, 2, 3, 4], [])
[1, 4, 9, 16]
iex(57)>

Slicing binaries. Strings are binaries.

Let us split COVID-19 corona virus 2019 at length 8.

iex(60)> <<initials::binary-size(8), full_name::binary>> = "COVID-19 corona virus 2019"
"COVID-19 corona virus 2019"
iex(61)> initials
"COVID-19"
iex(62)> full_name
" corona virus 2019"
iex(63)>

This can even get more useful when you know what you are looking for.

iex(70)> <<initials::binary-size(8), space::binary-size(1), full_name::binary>> = "COVID-19 corona virus 2019"
"COVID-19 corona virus 2019"
iex(71)> full_name
"corona virus 2019"
iex(72)> space
" "
iex(73)>

Reading values of of maps while matching clauses.

Get the value of a map or struct. From here you should notice that _ matches everything.

iex(65)> %{name: value_of_name} = %{name: "Danstan", age: 28}
%{age: 28, name: "Danstan"}
iex(66)> value_of_name
"Danstan"
iex(67)>

Even better. Match the value if its nil or something.

iex(67)> case %{name: "Danstan", age: 28} do
...(67)> %{name: value_of_name} -> value_of_name
...(67)> _ -> "No name"
...(67)> end
"Danstan"
iex(68)> case %{name: nil, age: 28} do
...(68)> %{name: nil} -> "Name is nil"
...(68)> _ -> "No name"
...(68)> end
"Name is nil"
iex(69)> case %{age: 28} do
...(69)> %{name: nil} -> "Name is nil"
...(69)> _ -> "No name"
...(69)> end
"No name"
iex(70)>

Use the power of pattern matching or just cross over.

Consider the code below. As you can see, I don not have to use if or case to check if the user is an map (object in OOP) or a struct, It will just work while still assigning the value to the left of my match. %{name: name} = user makes name become the value of property name in the map.

iex(1)> defmodule User do
...(1)>   def has_name?(%{name: name}) do
...(1)>     name
...(1)>   end
...(1)>   def has_name?(_), do: "No name"
...(1)> end
iex(2)> %{name: "Danstan"} |> User.has_name?()
"Danstan"
iex(3)> %{age: 28} |> User.has_name?()
"No name"
iex(4)>

Any time you can pattern match, its usually a good choice because it will make your code flow very on point and easy to follow and maintain. Using pattern matching for destructuring complex data structures makes code very simple and cute :). Happy Pattern Matching!

I hope this was helpful. Cheers!