Elixir is a dynamic, functional language designed for building scalable and maintainable applications
Elixir leverages the Erlang VM
low-latency
distributed
fault-tolerant
used for web development and embedded software
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
$ 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>
# script.exs
IO.puts "Hello World!"
# Execute it in shell
$ elixir script.exs
.exs for interpreted Elixir files
.ex for compiled Elixir files
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
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
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 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 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
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 is done with <>
iex> "foo" <> "bar"
"foobar"
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
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
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
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
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
iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
iex> [1, true, 2, false, 3, true] -- [true, false]
[1, 2, 3, true]
iex> hd([1, 2, 3])
1
iex> tl([1, 2, 3])
[2, 3]
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]
iex> {:atom, "any", "value", 3, 2.0}
{:atom, "any", "value", 3, 2.0}
iex> tuple_size {:atom, "any", "value", 3, 2.0}
5
iex> tuple = {:hello, "world"}
{:hello, "world"}
iex> elem(tuple, 1)
"world"
iex> put_elem(tuple, 1, 3.1416) # new tuple is returned
{:hello, 3.1416}
iex> tuple # original tuple was not modified
{:hello, "world"}
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
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
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
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"
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"}
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", "!"]
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 on lists
iex> [a, b, c] = [1, 2, 3]
[1, 2, 3]
iex> a
1
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]
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 ^ 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}
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
Bind the list variable to [1, 2, 3]
iex> list = [1, 2, 3]
[1, 2, 3]
iex> list
[1, 2, 3]
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
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
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"
Will this match?
[a, b] = [1, [2]]
Yes
a bound to 1
b bound to [2]
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
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
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, []}
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"
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"
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 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
iex> if(true, do: "hello", else: "bye")
"hello"
iex> unless(true, do: "hello", else: "bye")
"bye"
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
iex> if true do
...> "hello"
...> else
...> "world"
...> end
"hello"
iex> unless true do
...> "hello"
...> else
...> "bye"
...> end
"bye"
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
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]
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
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
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}
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}
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}]
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 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}
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
iex> defmodule Math do
...> def sum(a, b) do
...> a + b
...> end
...> end
iex> Math.sum(1, 2)
3
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)
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)
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
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
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
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
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
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
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!
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
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 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
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]
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
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
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
Miguel Cobá