Match Operator
Mix.install([
{:kino, github: "livebook-dev/kino", override: true},
{:kino_lab, "~> 0.1.0-dev", github: "jonatanklosko/kino_lab"},
{:vega_lite, "~> 0.1.4"},
{:kino_vega_lite, "~> 0.1.1"},
{:benchee, "~> 0.1"},
{:ecto, "~> 3.7"},
{:math, "~> 0.7.0"},
{:faker, "~> 0.17.0"},
{:utils, path: "#{__DIR__}/../utils"}
])
Navigation
Variable Binding
The match operator =
is a way of binding some data to a named variable that we can
use in place of that data throughout the rest of a program.
For example, you can store the number 9
in a variable named my_variable
and then
use my_variable
in place of 9.
my_variable = 9
my_variable + 1
You can name a variable almost anything and then use that name throughout the rest of your program. In the example above, we use my_variable
, but you can name a variable almost anything
as long as it doesn’t break certain rules.
a_different_variable_name = 9
a_different_variable_name + 1
Why is storing a value in a variable useful? Imagine you have a program that runs several calculations on a number. Let’s say the number 8.
8 + 7 - 8 * 10 * 8 + 8
If we want to run these operations on the number 7 instead, that suddenly becomes tedious to write.
A variable allows us to store a value and reuse it throughout the program.
my_number = 8
my_number + 7 - my_number * 10 * my_number + my_number
If the desired value changes, you only need to change the variable.
my_number = 7
my_number + 7 - my_number * 10 * my_number + my_number
Variables are also helpful for making programs more clear. There’s an anti-pattern in programming called magic values. Magic values are values in a program that don’t have a name but are important. You’ll see magic numbers and magic strings as common anti-patterns. An anti-pattern means something you should generally avoid doing in your programs otherwise, they become less clear and difficult to work with.
For example, imagine you’re working on a program that accepts payments, and you find the following code
100 * 1.12
What is 1.12? What is 100? These are examples of magic values. They might be important for the code to run properly, but the programmer has provided no context on what they are. This program would be a lot clearer if we used well-named variables.
item_cost = 100
tax_multiplier = 1.12
item_cost * tax_multiplier
Oh! So this is a program that takes the cost of an item and adds tax to it.
Naming Variables
Variable names must follow a few rules:
- They must start with a letter.
- They may contain valid alphanumeric characters.
-
They may end with predicates such as
!
or?
. - They may not contain spaces.
-
They may not contain certain special characters such as
$
.
In addition to those enforced rules, it’s conventional to separate words in a variable with an underscore _
and only use lowercase letters.
If you break the rules for naming a variable, your program will crash with an error. Much like naming atoms, memorizing the rules of naming variables is unimportant. You will develop an intuition for if your variable name is valid or not through repeated exposure. In practice, most variable names are made of only lowercase letters, underscores, and sometimes numbers.
In general, you should give your variables meaningful names That improve the clarity of your code.
Short generic variable names are often difficult to decipher unless there’s an established convention. For example, you’ll often see the variable i
to refer to an index.
Here are some example variable names that do not convey meaning.
t = "12:00"
c = 10
Instead, prefer verbose variable names to improve the clarity of your programs.
time = "12:00"
cost = 10
Unbound Variables
Using an unbound variable will crash your program.
a_variable_that_has_not_been_bound
If you look closely at the error, you’ll see undefined function a_variable_that_has_not_been_bound/0 (there is no such import)
When you use an unbound variable, Elixir assumes it’s a function that has not been defined. You haven’t covered functions yet, but you will learn more about them in future lessons.
For now, it’s enough to be familiar with this error so that if you see it, you’ll know you’re using an unbound variable.
Your Turn
In the Elixir cell below, create a variable hello
and bind it to the value "world"
. Replace nil
with your answer.
hello = nil
Utils.feedback(:hello, hello)
Mutation
Under the hood, the values bound to variables are stored on the computer in memory.
The variable is pointing to the value in that location.
flowchart LR
variable --> location
For example, a variable hello
with the value "world"
would store the string
"world"
somewhere in memory. memory is a hardware component on the computer.
You may have heard of RAM (Random Access Memory).
flowchart LR
hello --> w["world"]
Object-Oriented Programming languages allow you to mutate the actual value in memory.
So when we write hello = 4
, the data "world"
mutates into 4
.
flowchart LR
hello --> w["world"]
However, we do not allow mutation in a functional programming language like Elixir. So instead of mutating the data, we rebind the variable to new data.
flowchart
w["world"]
hello --> 4
That’s why we call data in Elixir immutable.
It may not be clear why this matters, and that’s ok. For now, it’s enough to be aware that data is immutable, as you will have more opportunities to see the impact of immutability in future lessons.
Pattern Matching
In Elixir, the =
sign is the match operator. In other programming languages, you may call it the assignment operator.
Elixir calls =
the match operator because it uses pattern matching to bind variables.
What is pattern matching? Well rather than simply assigning values to variables, Elixir allows
you to bind variables on the left-hand side of the =
. So long as it matches the same shape of the
right hand side of the =
.
What does that mean? Well, we already know we can use =
to bind the right hand side value to a variable.
my_tuple = {1, 2, 3}
But you can also use pattern matching to bind values within a data type.
{one, two, three} = {1, 2, 3}
two
So long as the expression on the left side of match operator =
matches the pattern of the right side,
you can bind variables to values.
Here’s the same concept with a list
[hero, secret_identity] = ["Spider Man", "Peter Parker"]
hero
This binds a new variable. If you aren’t concerned about a value then you can use underscore _
to ignore it.
[_, secret_identity] = ["Spider Man", "Peter Parker"]
secret_identity
However, if the left hand side doesn’t match the right hand side, then the code will crash with a MatchError.
# The following crashes because a list with 1 element does not match a list with two elements.
[name] = ["Spider Man", "Peter Parker"]
You can pattern match with any kind of data. So long as the left side matches the right side.
Your Turn
Use pattern matching to bind "jewel"
to a variable called jewel
.
Replace jewel
with your answer.
[_, _, jewel] = [1, 2, "jewel"]
Utils.feedback(:jewel, jewel)
Debugging
Often times it’s useful to debug your code by knowing what the value of variables are.
We can use IO.inspect
to log values in our code. IO.inspect
returns the value given to it.
my_value = "hello"
IO.inspect(my_value)
We can wrap a value in IO.inspect
. So long as we do this correctly, IO.inspect
will
print the value in our code without affecting it’s behavior.
my_value = IO.inspect("hello")
my_value
Your Turn
Use IO.inspect
to print the hello
variable.
hello = "world!"
IO.inspect(hello)
Commit Your Progress
Run the following in your command line from the project folder to track and save your progress in a Git commit.
$ git add .
$ git commit -m "finish match operator section"