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

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 with String types to calculate the length of a defined String

Strings have other functions available, let’s try another:

String.upcase(name)

Result:

Now type this:

String.starts_with?(name, "J")

Result (trueis 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

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 that first does not equal second
  • < checks that first is less than second
  • first is greater than or equal to second

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 merge2 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 keys model , colour , make (for example, the value of make would be toyota , the value of colour would be red and the model would be corolla)
  • Now use the Map function put to add another attribute with key year and value of 2010 (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 modulehas the following syntax

defmodule ModuleName do
# code
end

And in this example, a functionhas 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

Engineering Leader | Writer | Speaker | Traveller. Passionate about growing opportunities for people in Tech.