Learn
Contents
- Why?
- What?
- How?
- Learn Elixir
- Commands
- Basic Types
- Functions and Modules
- Generate an Elixir Project
- Documentation
- Testing
- Formatting
- Data Structures
- Further Resources
Why?
Key Advantages
- Scalability
- Speed
- Compiled and run on the Erlang VM ("BEAM"). (Renowned for efficiency)
- Much better "garbage collection" than virtually any other VM
- Many tiny processes (as opposed to "threads" which are more difficult to manage)
- Functional language with dynamic typing
- Immutable data so "state" is always predictable!
- High reliability, availability and fault tolerance (because of Erlang) means apps built with elixir are run in production for years without any "downtime"!
- Real-time web apps are "easy" (or at least easier than many other languages!) as WebSockets & streaming are baked-in
Things will go wrong with code, and Elixir provides supervisors which describe how to restart parts of your system when things don't go as planned.
What?
Video Introductions
If you have the time, these videos give a nice contextual introduction into what Elixir is, what it's used for and how it works:
- Code School's Try Elixir, 3 videos (25mins 🎥 plus exercises, totalling 90mins). The 'Try' course is free (there is an extended paid for course).
- Pete Broderick's Intro to Elixir (41 mins 🎥)
- Jessica Kerr's Elixir Should Take Over the World (58 mins 🎥)
Not a video learner? Looking for a specific learning? https://elixirschool.com/ is an excellent, free, open-source resource that explains all things Elixir 📖 ❤️.
How?
Before you learn Elixir as a language you will need to have it installed on your machine.
To do so you can go to http://elixir-lang.org/install.html or follow our guide here:
Installation:
Mac:
brew install elixir
Ubuntu:
- Add the Erlang Solutions repo:
wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i erlang-solutions_1.0_all.deb
- Run:
sudo apt-get update
- Install the Erlang/OTP platform and all of its applications:
sudo apt-get install esl-erlang
- Install Elixir:
sudo apt-get install elixir
Windows:
-
Web installer
- Download the installer
- Click next, next, ..., finish
-
Chocolatey (Package Manager)
choco install elixir
Learn Elixir
Commands
-
After installing Elixir you can open the interactive shell by typing
iex
. This allows you to type in any elixir expression and see the result in the terminal. -
Type in
h
followed by thefunction
name at any time to see documentation information about any given built-in function and how to use it. E.g If you typeh round
into the (iex) terminal you should see something like this:
- Typing
i
followed by the value name will give you information about a value in your code:
Basic Types
This section brings together the key information from Elixir's Getting Started documentation and multiple other sources. It will take you through some examples to practice using and familiarise yourself with Elixir's 7 basic types.
Elixir's 7 basic types:
integers
floats
booleans
atoms
strings
lists
tuples
Truthiness
All values / types in Elixir apart from nil
and false
are truthy:
if 0 do "This is happening" end # "This is happening"
if "False" do "This is still happening" end # "This is still happening"
if false do "Not happening" end # nil
Numbers
Type 1 + 2
into the terminal (after opening iex
):
iex> 1 + 2
3
More examples:
iex> 5 * 5
25
iex> 10 / 2
5.0
# When using the `/` with two integers this gives a `float` (5.0).
# If you want to do integer division or get the division remainder
# you can use the `div` or `rem` functions
iex> div(10, 2)
5
iex> div 10, 2
5
iex> rem 10, 3
1
Booleans
Elixir supports true
and false
as booleans.
iex> true
true
iex> false
false
iex> is_boolean(true)
true
iex> is_boolean(1)
false
Atoms
Atoms are constants where their name is their own value (some other languages call these Symbols).
iex> :hello
:hello
iex> :hello == :world
false
true
and false
are actually atoms in Elixir
Names of modules in Elixir are also atoms. MyApp.MyModule
is a valid atom, even if no such module has been declared yet.
iex> is_atom(MyApp.MyModule)
true
Atoms are also used to reference modules from Erlang libraries, including built-in ones.
iex> :crypto.strong_rand_bytes 3
<<23, 104, 108>>
One popular use of atoms in Elixir is to use them as messages
for pattern matching.
Let's say you have a function which processes an http
request.
The outcome of this process is either going to be a success or an error.
You could therefore use atoms to indicate whether
or not this process is successful.
def process(file) do
lines = file |> split_lines
case lines do
nil ->
{:error, "failed to process file"}
lines ->
{:ok, lines}
end
end
Here we are saying that the method,
process/1
will return a tuple response.
If the result of our process is successful, it will return {:ok, lines}
,
however if it fails (e.g. returns nil) then it will return an error.
This will allows us to pattern match on this result.
{:ok, lines} = process('text.txt')
Thus, we can be sure that we will always have the lines returned to us and never a nil value (because it will throw an error). This becomes extremely useful when piping multiple methods together.
Strings
Strings are surrounded by double quotes.
iex> "Hello World"
"Hello world"
# You can print a string using the `IO` module
iex> IO.puts "Hello world"
"Hello world"
:ok
Lists
Elixir uses square brackets to make a list.
iex> myList = [1,2,3]
iex> myList
[1,2,3]
iex> length(myList)
3
# concatenating lists together
iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
# removing items from a list
iex> [1, true, 2, false, 3, true] -- [true, false]
[1, 2, 3, true]
Lists are enumerable and can use the Enum module to perform iterative functions such as mapping.
Tuples
Elixir uses curly brackets to make a tuple.
Tuples are similar to lists but are not suited to data sets that need to be updated or added to regularly.
iex> tuple = {:ok, "hello"}
{:ok, "hello"}
# get element at index 1
iex> elem(tuple, 1)
"hello"
# get the size of the tuple
iex> tuple_size(tuple)
2
Tuples are not enumerable and there are far fewer functions available
in the Tuple module. You can reference tuple values by index but you cannot iterate over them.
If you must treat your tuple as a list,
then convert it using Tuple.to_list(your_tuple)
Lists or Tuples?
If you need to iterate over the values use a list.
When dealing with large lists or tuples:
-
Updating
alist
(adding or removing elements) is fast -
Updating
atuple
is slow -
Reading
alist
(getting its length or selecting an element) is slow -
Reading
atuple
is fast
source: http://stackoverflow.com/questions/31192923/lists-vs-tuples-what-to-use-and-when
Functions and Modules
Anonymous functions
Anonymous functions start with fn
and end with end
.
iex> add = fn a, b -> a + b end
iex> add.(1, 2)
3
Note a dot .
between the variable add
and parenthesis is required
to invoke an anonymous function.
In Elixir, functions are first class citizens
meaning that they can
be passed as arguments to other functions the same way integers and strings can.
iex> is_function(add)
true
This uses the inbuilt function is_function
which checks to see if
the parameter passed is a function and returns a bool.
Anonymous functions are closures (named functions are not)
and as such they can access variables
that are in scope when the function is defined.
You can define a new anonymous function that uses the add
anonymous function we have previously defined:
iex> double = fn a -> add.(a, a) end
iex> double.(5)
10
These functions can be useful but will no longer be available to you.
If you want to make something more permanent then you can create a module
.
Modules
With modules you're able to group several functions together. Most of the time it is convenient to write modules into files so they can be compiled and reused.
Get started by creating a file named math.ex
,
open it in your text editor and add the following code
defmodule Math do
def sum(a, b) do
a + b
end
end
In order to create your own modules in Elixir, use the defmodule
macro,
then use the def
macro to define functions in that module.
So in this case the module is Math
and the function is sum
.
Once this is saved the file can be compiled by typing elixirc
into the terminal followed by the file name.
$ elixirc math.ex
This will generate a file named Elixir.Math.beam
containing the bytecode
for the defined module. If we start iex
again, our module definition
will be available (provided that iex
is started
in the same directory the bytecode file is in):
iex> Math.sum(1, 2)
3
Generating your first Elixir project
To get started with your first Elixir project you need to make use of the Mix build tool that comes straight out of the box. Mix allows you to do a number of things including:
- Create projects
- Compile projects
- Run tasks
- Testing
- Generate documentation
- Manage dependencies
To generate a new project follow these steps:
Initialise a project by typing the following command in your terminal, replacing [project_name] with the name of your project:
> mix new [project_name]
We have chosen to call our project 'animals'
This will create a new folder with the given name of your project and should also print something that looks like this to the command line:
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/animals.ex
* creating test
* creating test/test_helper.exs
* creating test/animals_test.exs
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:
cd animals
mix test
Run "mix help" for more commands.
Navigate to your newly created directory:
> cd animals
Open the directory in your text editor. You will be able to see that Elixir has generated a few files for us that are specific to our project:
lib/animals.ex
test/animals_test.ex
animals.ex
file in the lib directory. You should already see some hello-world
boilerplate like this:
Open up the defmodule Animals do
@moduledoc """
Documentation for Animals.
"""
@doc """
Hello world.
## Examples
iex> Animals.hello
:world
"""
def hello do
:world
end
end
Elixir has created a module with the name of your project along with a function
that prints out a :world
atom when called. It's also added boilerplate for
module and function documentation. (we will go into more detail about
documentation later)
Let's test out the boilerplate code. In your project directory type the following command:
> iex -S mix
What this basically means is, "Start the elixir interactive terminal and compile
with the context of my current project". This allows you to access modules and
functions created within the file tree.
Call the hello-world
function given to us by Elixir. It should print out the
:world
atom to the command line:
> Animals.hello
# :world
Animals
module. Replace the hello-world
method with the following:
Let's start to create our own methods in the @doc """
create_zoo returns a list of zoo animals
## Examples
iex> Animals.create_zoo
["lion", "tiger", "gorilla", "elephant", "monkey", "giraffe"]
"""
def create_zoo do
["lion", "tiger", "gorilla", "elephant", "monkey", "giraffe"]
end
To run our new code we will first have to recompile our iex
. This can be done
by typing:
> recompile()
Now we will have access to the create_zoo
method. Try it out in the command line:
> Animals.create_zoo
# ["lion", "tiger", "gorilla", "elephant", "monkey", "giraffe"]
Animals
module. Let's say that you're visiting the zoo but you can't decide which order to view the animals. We can create a randomise
function that takes a list of animals and returns a new list with a random order:
Let's extend the @doc """
randomise takes a list of zoo animals and returns a new randomised list with
the same elements as the first.
## Examples
iex> zoo = Animals.create_zoo
iex> Animals.randomise(zoo)
["monkey", "tiger", "elephant", "gorilla", "giraffe", "lion"]
"""
def randomise(zoo) do
Enum.shuffle(zoo)
end
Note we are making use of a pre-built module called Enum
which has a list of
methods that you can use on enumerables such as lists. Documentation for Enum
methods can be found here
Animals
module. Let's say that we want to find out if our zoo contains an animal:
Let's add another method to the @doc """
contains? takes a list of zoo animals and a single animal and returns a boolean
as to whether or not the list contains the given animal.
## Examples
iex> zoo = Animals.create_zoo
iex> Animals.contains?(zoo, "gorilla")
true
"""
def contains?(zoo, animal) do
Enum.member?(zoo, animal)
end
NOTE: It's convention when writing a function that returns a boolean to add a question mark after the name of the method.
Pattern matching example: Let's create a function that takes a list of animals and the number of animals that you'd like to see and then returns a list of animals.
@doc """
see_animals takes a list of zoo animals and the number of animals that
you want to see and then returns a list
## Examples
iex> zoo = Animals.create_zoo
iex> Animals.see_animals(zoo, 2)
["monkey", "giraffe"]
"""
def see_animals(zoo, count) do
# Enum.split returns a tuple so we have to pattern match on the result
# to get the value we want out
{_seen, to_see} = Enum.split(zoo, -count)
to_see
end
What if we want to save our list of animals to a file? Let's write a function that will do this for us:
@doc """
save takes a list of zoo animals and a filename and saves the list to that file
## Examples
iex> zoo = Animals.create_zoo
iex> Animals.save(zoo, "my_animals")
:ok
"""
def save(zoo, filename) do
# erlang is converting the zoo list to something that can be written to the
# file system
binary = :erlang.term_to_binary(zoo)
File.write(filename, binary)
end
In your command line run the following:
> zoo = Animals.create_zoo
> Animals.save(zoo, "my_animals")
This will create a new file in your file tree with the name of the file that you specified in the function. It will contain some odd characters for example this is what gets returned for our animals file:
�l\����m����lionm����tigerm����gorillam����elephantm����monkeym����giraffej
Now let's write a function that will allow us to access that information again:
@doc """
load takes filename and returns a list of animals if the file exists
## Examples
iex> Animals.load("my_animals")
["lion", "tiger", "gorilla", "elephant", "monkey", "giraffe"]
iex> Animals.load("aglkjhdfg")
"File does not exist"
"""
def load(filename) do
# here we are running a case expression on the result of File.read(filename)
# if we receive an :ok then we want to return the list
# if we receive an error then we want to give the user an error-friendly message
case File.read(filename) do
{:ok, binary} -> :erlang.binary_to_term(binary)
{:error, _reason} -> "File does not exist"
end
end
Pipe Operator
What if we wanted to call some of our functions in succession to another? Let's create a function that creates a zoo, randomises it and then returns a selected number of animals to go and see:
@doc """
selection takes a number, creates a zoo, randomises it and then returns a list
of animals of length selected
## Examples
iex> Animals.selection(2)
["gorilla", "giraffe"]
"""
def selection(number_of_animals) do
# We are using the pipe operator here. It takes the value returned from
# the expression and passes it down as the first argument in the expression
# below. see_animals takes two arguments but only one needs to be specified
# as the first is provided by the pipe operator
Animals.create_zoo
|> Animals.randomise
|> Animals.see_animals(number_of_animals)
end
Now that we have the functionality for our module, let's take a look at the documentation that we have written and how we can maximise its use.
Documentation
When we created a new project with mix, it created a file for us called mix.exs
which is referred to as the 'MixFile'. This file holds information about our
project and its dependencies.
At the bottom of the file it gives us a function called deps
which manages all
of the dependencies in our project. To install a third party package we need to
manually write it in the deps function (accepts a tuple of the package name and
the version) and then install it in the command line. Let's install ex_doc
as
an example:
Add the following to the deps function in your mix.exs
file:
defp deps do
[
{:ex_doc, "~> 0.12"}
]
end
Then in the command line quit your iex
shell and enter the following to install
the ex_docs
dependency:
> mix deps.get
You might receive an error saying: Could not find Hex, which is needed to build dependency :ex_doc Shall I install Hex? (if running non-interactively, use: "mix local.hex --force") [Yn]
.
If you do then just enter y
and then press enter. This will install the
dependencies that you need.
Once ex_docs
has been installed then run the following command to generate
documentation (make sure you're not in iex
):
> mix docs
This will generate documentation that can be viewed if you copy the file path of
the index.html
file within the newly created doc
folder and then paste it in
your browser. If you have added documentation to your module and functions as per
the examples above, you should see something like the following:
It looks exactly like the format of the official Elixir docs because they used the
same tool to create theirs. Here is what the method documentation should look like
if you click on Animals
:
This is an incredibly powerful tool that comes baked-in with elixir. It means that other developers who are joining the project can be brought up to speed incredibly quickly!
Testing
When you generate a project with Elixir it automatically gives you a number of
files and directories. One of these directories is called test
and it holds two
files like should have names like:
[project_name]_test.exs
test_helper.exs
Our first file was called animals_test.exs
and it contained some boilerplate that
looks like:
defmodule AnimalsTest do
use ExUnit.Case
doctest Animals
test "the truth" do
assert 1 + 1 == 2
end
end
NOTE: It automatically includes a line called doctest Animals
. What this means
is that it can run tests from the examples in the documentation that you write for
your functions
To run the tests enter the following in your terminal:
mix test
It should print out whether the tests pass or fail.
Let's add some tests of our own. Firstly let's write a test for the Animals.randomise
function. The reason why we wouldn't want to write a doctest for this is because
the output value changes everytime you call it. Here's how we would write a test
for that type of function:
In the animals_test.exs
file, remove the boilerplate "the truth" test and then
add this:
test "randomise" do
zoo = Animals.create_zoo
assert zoo != Animals.randomise(zoo)
end
NOTE: you do not need to install and require any external testing frameworks.
It all comes with the Elixir package. Simply write test
followed by a string
representing what you are trying to test and then write your assertion.
The test above isn't completely air-tight. Elixir provides you with assertions that can help deal with things like this. The test could be re-written like so:
test "randomise" do
zoo = Animals.create_zoo
refute zoo == Animals.randomise(zoo)
end
This is basically saying "prove to be false that zoo is equal to Animals.randomise(zoo)"
Formatting
In Elixir version 1.6 the mix format
task was introduced.
see: elixir-lang/elixir#6643
mix format
is a built-in way to format your Elixir code
according to the community-agreed consistent style.
This means all code will look consistent across projects
(personal, "work" & hex.pm packages)
which makes learning faster and maintainability easier!
At present, using the formatter is optional,
however most Elixir projects have adopted it.
To use the mix task in your project, you can either check files individually, e.g:
mix format path/to/file.ex
Or you can define a pattern for types of files you want to check the format of:
mix format "lib/**/*.{ex,exs}"
will check all the .ex
and .exs
files in the lib/
directory.
Having to type this pattern each time
you want to check the files is tedious.
Thankfully you can define the pattern in a config file
and then simply run mix format
and the pattern is read from the file.
In the root of your Elixir project, create a .formatter.exs
config file and paste the following:
[
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
Now when you run mix format
it will check the mix.exs
file
and all .ex
and .exs
files in the config
, lib/
and test
directories.
This is the most common pattern for running mix format. Unless you have a reason to "deviate" from it, it's a good practice to follow.
There is an easy way to "fix" any Elixir code that does not meet the formatting guidelines, simply run:
mix format
And your code will be cleaned up.
We recommend installing a plugin in your Text Editor to auto-format:
-
Atom Text Editor Auto-formatter: https://atom.io/packages/atom-elixir-formatter
-
Vim Elixir Fomatter: https://github.com/mhinz/vim-mix-format
-
VSCode: https://marketplace.visualstudio.com/items?itemName=sammkj.vscode-elixir-formatter
-
Read the
mix/tasks/format.ex
source to understand how it works: https://github.com/elixir-lang/elixir/blob/master/lib/mix/lib/mix/tasks/format.ex -
https://hashrocket.com/blog/posts/format-your-elixir-code-now
-
https://devonestes.herokuapp.com/everything-you-need-to-know-about-elixirs-new-formatter
Data Structures
Maps
Maps are very similar to Object literals in JavaScript. They have almost the same
syntax except for a %
sign. They look like this:
animal = %{
name: "Rex",
type: "dog",
legs: 4
}
Values can be accessed in a couple of ways. The first is by dot notation just like JavaScript:
name = animal.name
# Rex
The second way values can be accessed is by pattern matching. Let's say we wanted to assign values to variables for each of the key-value pairs in the map. We would write something that looks like this:
iex> %{
name: name,
type: type,
legs: legs
} = animal
# we now have access to the values by typing the variable names
iex> name
# "Rex"
iex> type
# "dog"
iex> legs
# 4
Updating a value inside a map
Due to the immutability of Elixir, you cannot update a map using dot notation for example:
iex> animal = %{
name: "Rex",
type: "dog",
legs: 4
}
iex> animal.name = "Max" # this cannot be done!
In Elixir we can only create new data structures as opposed to manipulating existing ones. So when we update a map, we are creating a new map with our new values. This can be done in a couple of ways:
- Function
- Syntax
- Using a function
We can update a map usingMap.put(map, key, value)
. This takes the map you want to update followed by the key we want to reassign and lastly the value that we want to reassign to the key:
iex> updatedAnimal = Map.put(animal, :name, "Max")
iex> updatedAnimal
# %{legs: 4, name: "Max", type: "dog"}
- Using syntax
We can use a special syntax for updating a map in Elixir. It looks like this:
iex> %{animals | name: "Max"}
# %{legs: 4, name: "Max", type: "dog"}
NOTE: Unlike the function method above, this syntax can only be used to UPDATE a current key-value pair inside the map, it cannot add a new key value pair.
tl;dr
Note: this is definately not a "reason" to switch programming languages, but one of our (totally unscientific) reasons for deciding to investigate other options for programming languages was the fact that JavaScript (with the introduction of ES2015) now has Six Ways to Declare a Function: https://rainsoft.io/6-ways-to-declare-javascript-functions/ which means that there is ambiguity and "debate" as to which is "best practice", Go, Elixir and Rust don't suffer from this problem. Sure there are "anonymous" functions in Elixir (required for functional programming!) but there are still only Two Ways to define a
function
(and both have specific use-cases), which is way easier to explain to a beginner than the JS approach. see: http://stackoverflow.com/questions/18011784/why-are-there-two-kinds-of-functions-in-elixir
Further resources:
- Crash Course in Elixir
- Elixir School, which is available translated at least partially in over 20 languages and functions as a great succinct guide to core concepts.
- Learn Elixir - List of Curated Resources
- Explanation video of Pattern Matching in Elixir
- Sign up to: https://elixirweekly.net/ for regular (relevant) updates!
- List of more useful resources and sample apps
- If you want to know what's next it's worth check out What's Ahead for Elixir? (53 mins) by José Valim (the creator of Elixir)
- Interview with José Valim (the creator of Elixir) on why he made it! https://www.sitepoint.com/an-interview-with-elixir-creator-jose-valim/
- What was "wrong" with just writing directly in Erlang? read: http://www.unlimitednovelty.com/2011/07/trouble-with-erlang-or-erlang-is-ghetto.tml
- While Elixir by itself is pretty amazing, where the language really shines is in the Phoenix Web Framework!! So once you know the basics of the language try learning Phoenix.
- Want to contribute to the Elixir ecosystem? Try to find a good place to dive in with Extracurricular, which highlights issues for Elixir projects across all skill levels.