Elixir

Introduction

Miguel Cobá / @MiguelCobaMtz

Elixir

Elixir is a dynamic, functional language designed for building scalable and maintainable applications

Erlang VM

Elixir leverages the Erlang VM

low-latency

distributed

fault-tolerant

used for web development and embedded software

Installation

http://elixir-lang.org/install.html


# Mac OS X
# Homebrew
$ brew install elixir

# Test install
$ elixir -v
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Elixir 1.2.1
						

Interactive Elixir IEx


$ iex
Erlang/OTP 18 [erts-7.2.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.1) - press Ctrl+C to exit (type h() ENTER for help)
iex> 1 + 2
3
iex> "Hello" <> " world!"
"Hello world!"
iex>
						

Running scripts


# script.exs
IO.puts "Hello World!"

# Execute it in shell
$ elixir script.exs
						

Conventions

.exs for interpreted Elixir files

.ex for compiled Elixir files

Basic Types

Elixir Types


iex> 1          # integer
iex> 1.0        # float
iex> 1.0e-10    # float
iex> true       # boolean
iex> :atom      # atom / symbol
iex> "elixir"   # string
iex> [1, 2, 3]  # list
iex> {1, 2, 3}  # tuple
iex> 0b1010     # binary
iex> 0x777      # octal
iex> 0x1F       # hexadecimal
						

Arithmetic


iex> 1 + 2
3
iex> 2 * 2
4
iex> 10 / 2 # / always returns float
5.0
iex> div(10, 2) # / integer division
5
iex> div 10, 2 # parentheses are optional
5
iex> rem 10, 3 # remainder
1
						

Booleans and predicates


iex> true
true
iex> false
false
iex> true == false
false
iex> is_boolean(true)
true
iex> is_integer(1)
true
iex> is_float(1.0e-10)
true
iex> is_number(1)
true
iex> is_number(1.0)
true
						

Atoms

Atoms are constants where their name is its own value (akin to symbols in other languages)


iex> :hello
:hello
iex> :hello == :world
false
iex> :hello == "hello" # atoms are not strings
false
iex> :"hello" # but you can create atoms from strings
:hello
iex> :hello == :"hello" # but you can create atoms from strings
true
						

String

String in Elixir are encoded in UTF-8 and delimited by double quotes


iex> "Cobá ö ç ø"
"Cobá ö ç ø"
iex> "hello\nworld"  # line breaks
"hello\nworld"
iex> "hello
...> world"
"hello\nworld"
iex> IO.puts "hello\nworld" # Print string with IO.puts/1
hello
world
:ok
						

String interpolation


iex> "1 + 2 = #{1+2}"
"1 + 2 = 3"
iex> "hello #{:world}"
"hello world"
iex> "Is 1 integer? #{is_integer(1)}"
"Is 1 integer? true"
						

String concatenation

String concatenation is done with <>


iex> "foo" <> "bar"
"foobar"
						

String internals

Strings in Elixir are represented internally by binaries which are sequences of bytes


iex> is_binary("hello")
true
iex> byte_size("Cobá ö ç ø") # number of bytes in a string
14
iex> String.length("Cobá ö ç ø") # number of chars (length) in the string
10
						

Anonymous Functions

Functions are delimited by the keywords fn and end


iex> add = fn a, b -> a + b end
#Function<12.54118792/2 in :erl_eval.expr/5>
iex> is_function(add)
true
iex> is_function(add, 2)
true
iex> is_function(add, 3)
false
						

Anonymous Functions

A dot (.) between the variable and parenthesis is required to invoke an anonymous function


iex> add.(1,2)
3
						

Anonymous functions are closures: they can access variables that are in scope when the function is defined


iex> add_two = fn a -> add.(a, 2) end
#Function<6.54118792/1 in :erl_eval.expr/5>
iex> add_two.(2)
4
						

Anonymous Functions

A variable assigned inside a function does not affect its surrounding environment


iex> x = 42
42
iex> (fn -> x = 0 end).()
0
iex> x
42
						

Lists

Elixir uses square brackets to specify a list of values

Values can be of any type

Lists are Linked Lists


iex> [1, 2, true, 3]
[1, 2, true, 3]
iex> length [1, 2, 3]
3
						

Lists

Concatenation


iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
						

Substraction


iex> [1, true, 2, false, 3, true] -- [true, false]
[1, 2, 3, true]
						

Lists

Head


iex> hd([1, 2, 3])
1
						

Tail


iex> tl([1, 2, 3])
[2, 3]
						

Lists

  • Lists are stored in memory as linked lists, meaning that each element in a list holds its value and points to the following element until the end of the list is reached
  • We call each pair of value and pointer a cons cell
  • We can construct a cons cell with |

Lists

List construction


iex> list = [1, 2]
[1, 2]
iex> new_list = [ 0 | list ] # head and pointer to next element separated by |
[0, 1, 2]
iex> [ 1 | []]
[1]
iex> [ 1 | [2]]
[1, 2]
iex> [ 1 | [2, 3]]
[1, 2, 3]
iex> [ 1 | [2 | [3 | [4 | []]]]]
[1, 2, 3, 4]
						

Tuples

  • Elixir uses curly brackets to define tuples
  • Like lists, tuples can hold any value
  • Tuples store elements contiguously in memory
  • Accessing a tuple element per index or getting the tuple size is a fast operation
  • Tuples indexs start from zero

Tuples


iex> {:atom, "any", "value", 3, 2.0}
{:atom, "any", "value", 3, 2.0}
iex> tuple_size {:atom, "any", "value", 3, 2.0}
5
						

Tuples

Accessing elements


iex> tuple = {:hello, "world"}
{:hello, "world"}
iex> elem(tuple, 1)
"world"
						

Putting elements


iex> put_elem(tuple, 1, 3.1416) # new tuple is returned
{:hello, 3.1416}
iex> tuple # original tuple was not modified
{:hello, "world"}
						

List vs Tuples

Lists

  • Lists are stored in memory as linked lists
  • Accessing the length of a list is a linear operation
  • Updating a list is fast as long as we are prepending elements

Tuples

  • Tuples are stored contiguously in memory
  • Getting the tuple size or accessing an element by index is fast
  • Updating or adding elements to tuples is expensive (requires copying the whole tuple)

Pattern matching

The match operator

  • In Elixir, the = operator is actually called the match operator
  • The equals sign is not an assignment, is like an assertion
  • It succeeds if Elixir can find a way of making the left-hand side equal the right-hand side.

The match operator

In this case, the left-hand side is a variable and the right-hand side is an integer literal, so Elixir can make the match true by binding the variable a to value 1.


iex> a = 1
1
iex> a
1
						

The match operator

This is another match, and it passes. The variable a already has the value 1, so what’s on the left of the equals sign is the same as what’s on the right, and the match succeeds.

No new binding is performed


iex> 1 = a
1
						

The match operator

This raises an error. A variable can only be assigned on the left side of =

Elixir will only change the value of a variable on the left side of an equals sign. On the right a variable is replaced with its value

This is equivalent to 2 = 1 and causes an error


iex> 2 = a
** (MatchError) no match of right hand side value: 1
						

Pattern matching

The match operator is not only used to match against simple values, but it is also useful for destructuring more complex data types


# Pattern matching on tuples
iex> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex> a
:hello
iex> b
"world"
						

Pattern matching

A pattern match will error in the case the sides can’t match


iex> {a, b, c} = {:hello, "world"}
** (MatchError) no match of right hand side value: {:hello, "world"}
						

Pattern matching

A pattern match will error when comparing different types


iex> {a, b, c} = [:hello, "world", "!"]
** (MatchError) no match of right hand side value: [:hello, "world", "!"]
						

Pattern matching

But, it can match on specific values

This asserts that the left side will only match the right side when the right side is a tuple that starts with the atom :ok


iex> {:ok, result} = {:ok, 13}
{:ok, 13}
iex> result
13

iex> {:ok, result} = {:error, :oops}
** (MatchError) no match of right hand side value: {:error, :oops}
						

Pattern matching

Pattern matching on lists


iex> [a, b, c] = [1, 2, 3]
[1, 2, 3]
iex> a
1
						

Pattern matching

A list also supports matching on its own head and tail


iex> [head | tail] = [1, 2, 3]
[1, 2, 3]
iex> head
1
iex> tail
[2, 3]
						

The pin operator

Variables in Elixir can be rebound


iex> x = 1 # x bound to 1
1
iex> x = 2 # x rebound to 2
2
						

The pin operator

The pin operator ^ should be used when you want to pattern match against an existing variable’s value rather than rebinding the variable


iex> x = 1
1
iex> ^x = 2
** (MatchError) no match of right hand side value: 2
iex> {y, ^x} = {2, 1}
{2, 1}
iex> y
2
iex> {y, ^x} = {2, 2} # this is equivalent to {y, 1} = {2, 2}, that won't match
** (MatchError) no match of right hand side value: {2, 2}
						

Underscore

If you don’t care about a particular value in a pattern, the common practice is to bind those values to the underscore, _


iex> [h|_] = [1, 2, 3]
[1, 2, 3]
iex> h
1
						

Pattern matching exercise 1

Bind the list variable to [1, 2, 3]


iex> list = [1, 2, 3]
[1, 2, 3]
iex> list
[1, 2, 3]
						

Pattern matching exercise 2

Bind the a to 1, b to 2 and c to 3 in [1, 2, 3]


iex> [a, b, c] = [1, 2, 3]
[1, 2, 3]
iex> a
1
iex> b
2
iex> c
3
						

Pattern matching exercise 3

Bind the a to 1, b to 2 and c to 3 if variable list is already bound to [1, 2, 3]


iex> [ a, b, c] = list
[1, 2, 3]
iex> a
1
iex> b
2
iex> c
3
						

Pattern matching exercise 4

Bind each value to a variable, ignoring the last list, from [:atom, "string", {:some, "tuple"}, [1, 2, 3]]


iex> [atom, binary, {:some, tuple_value}, _] = [:atom, "string", {:some, "tuple"}, [1, 2, 3]]
[:atom, "string", {:some, "tuple"}, [1, 2, 3]]
iex> atom
:atom
iex> binary
"string"
iex> tuple_value
"tuple"
						

Pattern matching exercise 5

Will this match?


[a, b] = [1, [2]]
						

Yes

a bound to 1

b bound to [2]

Pattern matching exercise 6

Will this match?


[a, _b] = [1, [2]]
						

Yes

a bound to 1

_b bounded but a warning shown by compiler if used. Should be ignored

Pattern matching exercise 7

Will this match?


[:a, b] = [:"a", [2]]
						

Yes

b bound to [2]

The first element is matched literally on both sides but no binding is performed

Pattern matching exercise 8

Will this match?


{^a, 2, []} = {2, 2, []}
						

No

a bound to 1 and the pin operator was used to avoid rebinding a


iex> {^a, 2, []} = {2, 2, []}
** (MatchError) no match of right hand side value: {2, 2, []}
						

Control-flow structures

case

Allows us to compare a value against many patterns until we find a matching one

Execute clause body corresponding to the first clause that matches

If no clause matches, an error is raised.


iex> case {1, 2, 3} do
...>   {4, 5, 6} ->
...>     "This clause won't match"
...>   {1, x, 3} ->
...>     "This clause will match and bind x to 2 in this clause"
...>   _ ->
...>     "This clause would match any value"
...> end
"This clause will match and bind x to 2 in this clause"
						

case

Clauses also allow extra conditions to be specified via guards


iex> case {1, 2, 3} do
...>   {1, x, 3} when x > 0 ->
...>     "Will match"
...>   _ ->
...>     "Would match, if guard condition were not satisfied"
...> end
"Will match"
						

cond

Evaluates the expression corresponding to the first clause that evaluates to a truthy value

Raises an error if all conditions evaluate to nil or false


iex> cond do
...>   1 + 1 == 1 ->
...>     "This will never match"
...>   2 * 2 != 4 ->
...>     "Nor this"
...>   true ->
...>     "This will"
...> end
						

if/unless

if and unless are macros in Elixir

They expect the first argument to be a condition and the second argument to be a keyword list


iex> if(true, do: "hello world")
"hello world"
iex> unless(true, do: "hello world")
nil
						

if/unless


iex> if(true, do: "hello", else: "bye")
"hello"
iex> unless(true, do: "hello", else: "bye")
"bye"
						

if/unless

It's also possible to pass a block to the if/else macros


iex> if true do
...>   "hello"
...> end
"hello"
iex> unless true do
...>   "hello"
...> end
nil
						

if/unless


iex> if true do
...>   "hello"
...> else
...>   "world"
...> end
"hello"
iex> unless true do
...>   "hello"
...> else
...>   "bye"
...> end
"bye"
						

Keyword lists

A keyword list is a list of tuples where the first item of each tuple (i.e. the key) is an atom


iex> list = [{:a, 1}, {:b, 2}]
[a: 1, b: 2]
iex> list == [a: 1, b: 2]  # Syntactic sugar
true
iex> list[:a]
1
						

Keyword lists

Adding values to a keyword list


iex> list ++ [c: 3]
[a: 1, b: 2, c: 3]
iex> [a: 0] ++ list
[a: 0, a: 1, b: 2]
						

Keyword lists

Values added to the front are the ones fetched on lookup


iex> new_list = [a: 0] ++ list # another tuple with same key added to front
[a: 0, a: 1, b: 2]
iex> new_list[:a]
0
						

Maps

A map is a collection of key/value pairs

A map is created using the %{}

Maps allow any value as a key.

Maps’ keys do not follow any ordering.


iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> map[:a]
1
iex> map[2]
:b
iex> map[:c]
nil
						

Maps and pattern matching

When a map is used in a pattern, it will always match on a subset of the given value

A map matches as long as the keys in the pattern exist in the given map. Therefore, an empty map matches all maps.


iex> %{} = %{:a => 1, 2 => :b}
%{:a => 1, 2 => :b}
iex> %{:a => a} = %{:a => 1, 2 => :b}
%{:a => 1, 2 => :b}
iex> a
1
iex> %{:c => c} = %{:a => 1, 2 => :b}
** (MatchError) no match of right hand side value: %{2 => :b, :a => 1}
						

Maps and variables

Variables can be used when accessing, matching and adding map keys


iex> n = 1
1
iex> map = %{n => :one}
%{1 => :one}
iex> map[n]
:one
iex> map[1]
:one
iex> %{^n => :one} = %{1 => :one, 2 => :two, 3 => :three} # will match
%{1 => :one, 2 => :two, 3 => :three}
						

The Map Module

Convenience functions to manipulate maps


iex> Map.get(%{:a => 1, 2 => :b}, :a)
1
iex> Map.to_list(%{:a => 1, 2 => :b})
[{2, :b}, {:a, 1}]
						

Maps and keyword syntax

When all the keys in a map are atoms, you can use the keyword syntax for convenience


iex> map = %{a: 1, b: 2}
%{a: 1, b: 2}
						

Maps and dot syntax

Maps is that they provide their own syntax for updating and accessing atom keys


iex> map = %{:a => 1, 2 => :b}
%{:a => 1, 2 => :b}
iex> map.a
1
iex> map.c
** (KeyError) key :c not found in: %{2 => :b, :a => 1}
iex> %{map | :a => 2} # updating the map
%{:a => 2, 2 => :b}
iex> %{map | :c => 3} # key must exist before updating it
** (KeyError) key :c not found in: %{2 => :b, :a => 1}
						

Modules

Modules

We group several functions into modules

We use the defmodule macro to define a new module

We use the def macro to define a function inside a module

Modules


iex> defmodule Math do
...>   def sum(a, b) do
...>     a + b
...>   end
...> end

iex> Math.sum(1, 2)
3
						

Modules

We use the defp macro to define a private function inside a module

A function defined with def/2 can be invoked from other modules while a private function can only be invoked locally


defmodule Math do
  def sum(a, b) do
    do_sum(a, b)
  end

  defp do_sum(a, b) do
    a + b
  end
end

IO.puts Math.sum(1, 2)    #=> 3
IO.puts Math.do_sum(1, 2) #=> ** (UndefinedFunctionError)
						

Guards and multiple guards in functions

Function declarations also support guards and multiple clauses

If a function has several clauses, Elixir will try each clause until it finds one that matches.


defmodule Math do
  def zero?(0) do
    true
  end

  def zero?(x) when is_number(x) do # another declaration of zero? function with a guard
    false
  end
end

IO.puts Math.zero?(0)       #=> true
IO.puts Math.zero?(1)       #=> false
IO.puts Math.zero?([1,2,3]) #=> ** (FunctionClauseError)
						

Functions and block syntax

Similar to constructs like if, named functions support both do: and do/end block syntax

do/end is just a convenient syntax for the keyword list format


defmodule Math do
  def zero?(0), do: true
  def zero?(x) when is_number(x), do: false
end
						

Function capturing

The notation name/arity to refer to functions can be used to retrieve a named function as a function type


iex> Math.zero?(0)
true
iex> fun = &Math.zero?/1
&Math.zero?/1
iex> is_function(fun)
true
iex> fun.(0)
true
						

Function capturing

Local or imported functions, like is_function/1, can be captured without the module


iex> fun2 = &is_function/1
&:erlang.is_function/1
iex> fun2.(fun)
true
iex> fun2.(fun2)
true
						

Function capturing

Note the capture syntax can also be used as a shortcut for creating functions

The &n represents the nth argument passed into the function


iex> fun3 = &(&1 + 1)
#Function<6.54118792/1 in :erl_eval.expr/5>
iex> fun3.(1)
2
						

&(&1+1) above is exactly the same as fn x -> x + 1 end

Function capturing

Capture syntax can be used for module functions, too


iex(3)> fun4 = &Kernel.max(-&1, -&2)
#Function<12.54118792/2 in :erl_eval.expr/5>
iex(4)> Kernel.max(1, 2)
2
iex(5)> fun4.(1, 2)
-1
						

Default arguments

Elixir support default arguments


defmodule Concat do
  def join(a, b, sep \\ " ") do
    a <> sep <> b
  end
end

IO.puts Concat.join("Hello", "world")      #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
						

Recursion

Loops

Functional languages rely on recursion to do loops: a function is called recursively until a condition is reached that stops the recursive action from continuing. No data is mutated in this process


defmodule Recursion do
  def print_multiple_times(msg, n) when n <= 1 do
    IO.puts msg
  end

  def print_multiple_times(msg, n) do
    IO.puts msg
    print_multiple_times(msg, n - 1)
  end
end

Recursion.print_multiple_times("Hello!", 3)
# Hello!
# Hello!
# Hello!
						

Recursion example 1

Map and reduce


defmodule Math do
  def sum_list([head|tail], accumulator) do
    sum_list(tail, head + accumulator)
  end

  def sum_list([], accumulator) do
    accumulator
  end
end

IO.puts Math.sum_list([1, 2, 3], 0) #=> 6
						

Functions exercise 1

Write a module with a function to calculate the factorial of n


defmodule Math do
	def factorial(n), do: do_factorial(n, 1)

	#defp do_factorial(1, acc), do: acc
	defp do_factorial(n, acc) when n == 1, do: acc
	defp do_factorial(n, acc), do: do_factorial(n - 1, acc * n)
end

IO.puts Math.factorial(50)
# 30414093201713378043612608166064768844377641568960512000000000000
						

The Enum module

The Enum module

The Enum module provides a huge range of functions to transform, sort, group, filter and retrieve items from enumerable


iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.map(%{1 => 2, 3 => 4}, fn {k, v} -> k * v end)
[2, 12]
iex> Enum.map(1..3, fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.reduce(1..3, 0, &+/2)
6
						

Eager vs lazy

All the functions in the Enum module are eager

Many functions expect an enumerable and return a list back


iex> odd? = &(rem(&1, 2) != 0)
#Function<6.80484245/1 in :erl_eval.expr/5>
iex> Enum.filter(1..3, odd?)
[1, 3]
						

Eager vs lazy

When performing multiple operations with Enum, each operation generates an intermediate list


iex> Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))
7500000000
						

The pipe operator |>

It takes the output from the expression on its left side and passes it as the first argument to the function call on its right side


iex> 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum
7500000000
						

The Stream module

Elixir provides the Stream module which supports lazy operations

Streams are useful when working with large, possibly infinite, collections


iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum
7500000000
						

Slides

http://miguelcoba.github.io/elixir-intro

Thank you!

Miguel Cobá

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.