Elixir Beginners Tutorial
This tutorial was originally written for the Women Who Code Sydney workshop https://www.meetup.com/Women-Who-Code-Sydney/events/254987164/
Skill level for this tutorial:
This tutorial is aimed at people who are new to coding
If you have coded before, you may want to try this tutorial: “Elixir Beginner II Tutorial” from this page https://tinyurl.com/wwc-elixir
What is Elixir?
Elixir is a functional programming language which started as an R&D project of Plataformatec and now used by companies such as The Bleacher Report, Pinterest, Moz, Expert360, Culture Amp and many others! (The syntax can appear Ruby-like at first and beginners may appreciate some of the similarities here)
What we’ll be covering
In this tutorial we will be covering a few topics:
- Data Types (String, Numeric, Boolean, Atom)
- Lists
- Maps
- Pattern Matching
- Conditionals (if, case, cond)
- Functions
- Pipe Operator
Elixir is a language with many features, but hopefully you’ll be able to get through the above in a couple of hours during the workshop and come back to learn more :)
Installation
If you are working off your computer, for this tutorial, you’ll need to have
- Elixir installed (https://elixir-lang.org/install.html)
For Windows users
This guide will help: https://www.youtube.com/watch?v=antnsMgA4Ro (see the 2:50 min mark and stop at 3:50 min mark)
Not installed or using an online editor:
If you do not have this installed you can still follow along, please see this guide to setup an online editor: https://tinyurl.com/elixir-ideone
Step 1: Let’s Start with Hello
We will first open the terminal and try some things out:
If you have come from the online editor setup (i.e. from here: https://tinyurl.com/elixir-ideone), please skip ahead to: Step 1.1 Let’s Code!
If you are on mac, look for an app called “terminal”
If you are on windows, look for “command”
Now in the terminal, type:
iex
You’ll get something like this
This is where we will write some Elixir code.
IEx is the Interactive Elixir Shell which lets you write code that runs and outputs the results to you.
Step 1.1 Let’s Code!
Now let’s try the following snippet. Type the following and hit return/enter:
IO.puts "hello"
(Reminder: if you are using the online editor, you need to click on the “Run” button)
The output will look something like this
What’s happened here? We’ve printed out the word “hello” onto the screen.
Step 2: Data Types
In programming languages, types are a way to define something.
Let’s explain with an example. To represent someone’s name like “John Doe”, you would have this represented as characters as a String type.
For now, we will introduce you to these types:
- String
- Numbers
- Boolean
2.1 Strings
In the terminal, type the following
name = "John Doe"
Then underneath, type this
IO.puts name
You should get something like this
What just happened?
Firstly — we created a variable called “name”, which is used to represent in this case, a name.
Variables are used to store values for example, a person’s age or their name.
Next — we then assigned a value to name, in this case “John Doe”. If you notice, we wrapped John Doe around quotes, defines the variable as a String type.
After that — we then printed the name onto the screen, using IO.puts
(a built-in Elixir function to print something onto the screen)
Next in the terminal, type the following (underneath):
IO.puts "Hi #{name}"
You should get
So we know that IO.puts
prints something to the screen. The next thing you’ll notice is that within the quotes, the name variable is wrapped between #{}
.
This is called String Interpolation — it’s a way to evaluate the value of a variable
in a String.
Next type the following
String.length(name)
The result:
As you can see, the number of characters in name 8
was printed on the terminal.
In this case length
is a function which counts the number of characters in a String.
What’s a function?
It’s code that has already been written to evaluate something. In our example above, the function
length
is available withString
types to calculate the length of a definedString
Strings have other functions available, let’s try another:
String.upcase(name)
Result:
Now type this:
String.starts_with?(name, "J")
Result (true
is returned to indicate that name starts with J
):
Now try
String.starts_with?(name, "A")
As you can see, the result false
indicates that name does not start with A
Try this:
String.reverse("hello")
For more String functions, check out https://hexdocs.pm/elixir/String.html
2.2 Numerical Data Types
Numeric Types are a way to represent numbers like whole numbers or numbers with a decimal point.
Let’s try this out, type:
age = 30
Then
height = 170.5
You should see the following:
You can see from the above that age
is a whole number (30) which is known as an Integer numeric type, and height
has decimal points (170.5), which is known as a Float numeric type.
Try the following:
is_integer(age)
You should get
Now try
is_float(height)
And then type
is_integer(height)
Both is_integer
and is_float
are a way to check the type of a variable
You can also use interpolation
here too:
IO.puts "John is #{age} and is #{height} cms tall."
Now type this underneath and run the code to try some arithmetic:
IO.puts age + 10
IO.puts age * 2
IO.puts age/2
You can also do this:
div(9,4) # divides 4 into 9 and returns the multiple i.e. 2
rem(9, 4) # returns the remainder i.e. 1
You can also leverage functions available to numerical types, for example Integer has a few functions you can try.
For example try:
require Integer
Integer.is_even(2)
Integer.is_odd(2)
From the above, we need to require
Integer to access the functions available.
For more functions available for Integer, check out https://hexdocs.pm/elixir/Integer.html
Exercise to try this yourself
- Create two
variables
(num1
andnum2
) and assign Integer values to them15
and20
- Then find the maximum value out of
num1
andnum2
using this function https://hexdocs.pm/elixir/Kernel.html#max/2
2.3 Booleans
Remember the example above where we checked if a number was even or odd? The result was true
or false
, which are actually Boolean
type.
Booleans
are only either true
or false
and used for decision making in code. You’ll see further in the tutorial
Let’s try a few things:
2 == 2
In the example above ==
represents equality, when we want to check if the left and right of ==
equal each other
In this case, 2 == 2
is equal and result is true
Let’s try another
5 == 9
Now let’s try
first = 1
second = 2
first == second
first != second
first < second
second >= first
Here you can see a few things:
!=
checks to see thatfirst
does not equalsecond
<
checks thatfirst
is less thansecond
first
is greater than or equal tosecond
And operators:
true && true
true && false
true and false
Or operators:
true || true
true || false
true or false
2.4 Atoms
An Atom
is a constant
in Elixir, whose name is it’s own value. A constant
cannot change in value.
For example
:age
:name
These are used in Elixir for things like error handling (e.g. to know if an error occurred), attributes on more complex types (e.g. a person’s age) and more. We will explore these more further in the tutorial.
Step 3: Lists
A List
is also a Data Type
in Elixir and let’s us hold more than one value.
Let’s try this:
names = ["Louise", "Tom", "Fred"]
You should get the following
You can see from the above example, that we have created a List
called names
containing Strings
You can also join Lists too
names ++ ["Judy", "Mac"]
You can remove items from the list as well
names -- ["Louise"]
You can also store more than one data type (including other lists)
list = [9, "ice cream", 3.5, [1, 2, 3]]
Lists
also have functions you can use, for example if you wanted to find the first and last elements of the list:
List.first(list)
List.last(name)
Exercise:
- Try to find the number of items in a list, by using the
length
function - Now try
List.replace(["a", "b", "c"], 1, "bb")
. What did you see? (Have a chat to your coach)
For more functions available to List, check out https://hexdocs.pm/elixir/List.html
Step 4. Maps
Maps
is a Data Structure
in Elixir and stores items with a key
and a corresponding value
Let’s take a look at an example:
Here we are using a Map
to represent a person with attributes name
, age
and city
. These are alsoAtoms
in the map, which are keys with corresponding values Judy
, 27
and Sydney
Let’s try the following:
person[:name]
As you can see name
is actually an Atom
which is used to access the person Map
, to find the value for the keyname
You can also use the following syntax
person.name
Exercise:
- Now you try finding the age from person
Maps have functions you can use too. For example you can find the keys of a Map, type:
Map.keys(person)
You should get the following:
You can merge
2 Maps together:
job_details = %{occupation: "Scientist", industry: "Medical Science"}Map.merge(person, job_details)
You should see both maps merged in the result
Exercise:
- Now create a new map named
car
with keysmodel
,colour
,make
(for example, the value ofmake
would betoyota
, the value ofcolour
would bered
and the model would becorolla
) - Now use the
Map
functionput
to add another attribute with keyyear
and value of2010
(hint: see this API doc https://hexdocs.pm/elixir/Map.html#put/3)
For more information on Map, check out: https://hexdocs.pm/elixir/Map.html
Step 5: Pattern Matching
Pattern Matching is a technique that matches the left hand to the right hand of the =
sign
In Elixir the =
sign is actually a match operator
Let’s try this example
a = 10
You should get the following
Now type
a
You should get
This looks like we’re assigning 10
to the variable a
.
However, what this is doing is matching the expression on the right to the left.
Let’s try this:
x = 1
^x = 2
^
is the pin operator — pattern matching on the variable x
value rather than rebinding x
(i.e. rather than re-setting the value of x
)
Try this:
Another example:
{x, y} = {10, 20}
x
y
Example that does not match
{y, 1} = {2, 2}
With the above example, the left and right do not match. 1 does not equal to 2.
Let’s try another example:
%{name: name, age: age} = %{name: "Penny", age: 42}
Then, type
age
And then
name
You should get this
As you can see the there was a match between both sides
Now try this
%{make: make} = %{make: "Toyota", colour: "red", model: "Corolla"}
Then type
make
For the example above, we were still able to match, as make
is a key in the map on the right.
The rest of the attributes are unassigned, as we were only interested in make
Let’s try another example
[first | the_rest] = [1, 2, 3]
Then type
first
Next type
the_rest
You should get this
You can see from the above, we were able to find the first
element in the list and the_rest
of the list by using pattern matching too.
Note: normally for this example, this is expressed as
[head | tail] = [1, 2, 3]
For more information on pattern matching, check out:
https://elixir-lang.org/getting-started/pattern-matching.html
https://www.tutorialspoint.com/elixir/elixir_pattern_matching.htm
Step 6. Conditionals
At times, you’ll need to have logic to determine what code to run in certain conditions.
6.1 If, else and unless conditions
Let’s try the following:
if (10 > 1) do
true
end
You should get something like this
What happened here? We checked if 10 was greater than 1, and if that was true
we returned true
.
Let’s try another example:
names = ["Betty", "Roger", "Sean", "Kim"]
Then type:
if (length(names) == 4) do
IO.puts "There are 4 names"
end
You should get something like the following
In this example we checked that the number of names in the list was 4.
Using else:
Here is another example to try (be careful with brackets and syntax here)
if (1 + 1 == 20) do
true
else
false
end
You should get something like this
What happened here?
We wanted to see if the result of 1 + 1 == 20
, because this was not true, the code followed the else
condition.
Using Unless
Here is another example
unless (1 + 1 == 20) do
true
end
You should see the following
As you can see, the result of 1 + 1
is not 20
(it was false), and unless
works by validating the opposite
of the result is true
6.2 Case
We may want to check against a result against different cases, for example:
num = 20
case num do
1 -> IO.puts "equals 1"
11 -> IO.puts "equals 11"
20 -> IO.puts "equals 20"
_ -> false
end
You should get the following:
Side note: The last case _
is a catch-all
where is nothing matches, the code will follow through to this case.
6.3 Cond
cond
looks similar to case
, however is a way to execute code like if else
.
For example:
cond do
num + 1 == 30 -> "Result is 30"
num + 1 == 40 -> "Result is 40"
num + 1 == 21 -> "Result is 21"
end
You should get the following
Step 7. Modules and Functions
Functions are a way to create code that can be re-usable.
Let’s try this
sum = fn (a, b) -> a + b end
Then type
sum.(1,2)
You should get:
This is an anonymous function
, as the function does not have a name and is enclosed in fn
and end
Modules
Functions can also live within a Module
(which can contain many functions)
Let’s try the following
defmodule Math do
def sum(a, b) do
a + b
end
end
You should get the following
The result means that a Module
called Math
was created.
The Math
module has a function
called sum
which takes 2 arguments
, sum
is a named function
In the example, a module
has the following syntax
defmodule ModuleName do
# code
end
And in this example, a function
has the following syntax
def function_name(arguments) do
# code
end
Let’s try this
Math.sum(10, 20)
Using default values
Let’s redefine the Math
module
defmodule Math do
def sum(a, b \\ 0) do
a + b
end
end
Here we have given a default
value in case b
is not passed
Now type
Math.sum(20)
You should get:
Because b
was not passed, the default value of 0
was used i.e. 20 + 0 = 20
Using Guards
You can also have functions to handle different cases.
Try the following:
defmodule HelloList do
def find_first(list) do
List.first(list)
end def find_first(list) when list == [] do
nil
end
end
In the example above, when
is the guard
condition, that checked if list
is an empty list.
Now type
HelloList.find_first([1,2])
You should get
Now try
HelloList.find_first([])
You should have
( nil
above means no value)
Using private functions
In functions you can have functions that are private
to a module
by defining this with defp
instead of def
Let’s redefine the module
defmodule HelloList do
def find_first(list) do
find_first_do(list)
end def find_first(list) when list == [], do: nil defp find_first_do(list) do
[first | _others] = list
first
end
end
Here we’ve added a private function which uses pattern matching
within the function to return the first element
Now type
HelloList.find_first([1,2])
You should get 1
as a result
Using Pattern Matching on Arguments
We can also use pattern matching on arguments of a function
Redefine the module again
defmodule HelloList do
def find_first(list) do
find_first_do(list)
end def find_first(list) when list == [], do: nil defp find_first_do([first | _others]) do
first
end
end
Now type
HelloList.find_first([1,2])
You should get 1
as a result
Step 8. Pipe Operator
Elixir also has something called a Pipe Operator
which is used to pass the result of one function to another function.
Let’s try an example:
Integer.to_string(12345)
Notice that the result is a String
Now let’s pipe the result (now a String
) to another function
Integer.to_string(12345) |> String.to_integer
You should get the following:
You can see the result was converted back to an Integer
Let’s try another example
String.split("hello", "")
First we should get
Now let’s count the length of this list
String.split("hello", "") |> length
You should get
Exercise: Now try converting hello
to uppercase using the |>
and String.upcase()
That’s It!
You’ve reached the end of the tutorial, hope you’ve enjoyed the tutorial