Posted Thursday, March 26th, 2020
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.
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.
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)>
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)>
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 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 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.
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)>
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)>
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)>
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)>
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!
Thank you for finding time to read my post. I hope you found this helpful and it was insightful to you. I enjoy creating content like this for knowledge sharing, my own mastery and reference.
If you want to contribute, you can do any or all of the following 😉. It will go along way! Thanks again and Cheers!