When you write greeting = "Hello"
in Python, JavaScript, or similar languages, you are ‘assigning’ the value "Hello"
to the variable greeting
. The =
used here is what is callend an assignment operator.
If you are new to Elixir, then it will come to you as a suprise that the expression greeting = "Hello"
in Elixir is not using an assignment operator. As a matter of fact, there’s no such thing as an assignment operator (=
) in Elixir!
In Elixir, assignment (and much more) is achieved by relying on different cases of pattern matching. Pattern matching is just about everywhere in an Elixir codebases. Obviously, it is to sweet and powerful to be not there.
The goal of this article is to tell you everything about using pattern matching with different data types. Once you know these basics, you can wrap up everything by seeing patterns and use cases in action in the follow up article Elixir Pattern-Matching Finale.
Before we start, repeat: There is no such thing as assignment in Elixir, but only pattern matching.
And binding a value to a variable name is just one of the many consequences of pattern matching.
1. Values match themselves and variable names match anything
1 = 1 # Output is 1 i.e. 1 matches 1.
Names match anything and bind to the matched value.
a = 1 # A will match anything, in this case, 1 and will bind to it
a == 1 # true
This gets the job of a typical assignment operator done. Everything else described below is extra goodies you get because of pattern-matching.
2. Matching based on data types:
Data types have special structures. Pattern-matching too, can be utilized differently based on the ‘structure’ of the data type you are dealing with.
Lists
[first, second | tail] = [1, 2, 3, 4]
first == 1 # true
remaining == [3, 4] # true
[:ok, a] = [:ok, "just about anything"]
a == "just about anything" #true
Of course, the matching should be exact for both right and left side. For instance, this won’t work:
[a, b] = [1, 2, 3] # `MatchError` is raised
Variable name can be reused as well.
[a, a, a] = [1, 1, 1] # will work
[a, a, a] = [1, 2, 3] # wont work
Of course, you may feel free to ignore things. As a convention, _
is used as a prefix (or fully qualified name in this case) for ignoring values.
[useful, _, _] = ["thing", nil, nil]
Tuples
No surprise, and not much different from a list.
{name, answer} = {"david", 42}
name == "david" # true
answer == 42 # true
The pin(^
) operator
Elixir does not limit you to just using constants in making patterns. You can also explicitly use variable names to match against the values they store. This is achived using the ping(^
) operator.
name = "david"
{^name, answer} = {"david", 42} # will pattern-match
{^name, answer} = {"dave", 42} # will raise `MatchError`
Strings
Even strings support pattern matching!
"I am " <> what = "I am annoying"
what == "annoying" #true
Maps
%{result: :ok} = %{result: :ok, value: 42} # No error but pretty much useless
%{result: result_val} = %{result: :ok, value: 42} # Now `result_val` is of some use
result_val == :ok #true
Structs
defmodule Response do
defstruct [:type, :status]
end
%Response{type: type, status: status} = %Response{type: "GET", status: "200 OK"}
type == "GET" # true
status = "200 OK" # true
Structs are a bit more interesting. In the above case, they are not much different from maps, but a specific version of pattern matching on structs can allow you to enforce type-checking in function signature itself (mentioned in the follow-up post).
Outro
Now equipped with all the basics, you can proceed to the next post and see all the pattern matching in action- Elixir Pattern-Matching Finale.