Elixir
(learning by observing)
Erlang
hot code reload
pattern matching
behaviours
protocol
typespecs
processes
Supervisor
GenServer
macro
scheduler
BEAM
mailbox
pid
Registry
OTP
special forms
mix
distilery
node
cluster
via
monitor
:observer
Agent
Task
ETS
Mnesia
umbrella
(un)quote
hex
binaries
with
capture
multiple heads
|>
let is crash
tail call optimization
guards
struct
use
pin
Scalable
- code runs in lightweight processes
- communication via asynchronous messages
- immutable state not shared between processes
- processes cooperate to provide complete service
- VM can efficiently parallelise process execution across all available cores(vertical scaling)
- processes can easily communicate with processes on other machines(horizontal scaling)
Distributed
- communication between processes works the same way no matter if they communicate locally on the same machine or over the network on separate nodes
- adds to the overall resiliency of the system, when one nodes crashes, others can take over the load
Fault tolerant
- processes are completely independent
- state is not shared
- error in one process doesn't affect other processes in the system
- isolating error effects using supervisors
- generally speaking, error in one part of the system should never bring down the whole system
BEAM scheduling
BEAM process
Scheduler #1
Scheduler #2
Scheduler #3
Scheduler #4
Setup
[vrabac - ~]$ iex
Erlang/OTP 20 [erts-9.0] [source] [64-bit]
Interactive Elixir (1.6.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
Erlang interop
- access to all Erlang standard libraries
- call to Erlang modules must be prefixed with colon(:)
- no peformance penalty for calling Erlang functions
iex(1)> :crypto.rand_uniform(1, 10)
6
Running scripts
# main.exs
a = 1
b = 2
IO.inspect("#{a + b}")
$> elixir main.exs
"3"
- by convention, one time scripts are stored in .exs files
Interactive shell
# clock.exs
defmodule Clock do
def now() do
IO.inspect DateTime.utc_now()
end
end
$> iex clock.exs
Erlang/OTP 20 [erts-9.0] [source] [64-bit]
Interactive Elixir (1.6.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Clock.now
#DateTime<2019-03-31 05:44:40.524135Z>
Recompile
[vrabac - running-scripts]$ iex clock.exs
Erlang/OTP 20 [erts-9.0] [source] [64-bit]
Interactive Elixir (1.6.1) - press Ctrl+C to exit (type h() ENTER for help)
# Compile a file
iex(2)> c "clock.exs"
warning: redefining module Clock (current version defined in memory)
clock.exs:1
[Clock]
# Recompile and reload a module
iex(3)> r Clock
warning: redefining module Clock (current version defined in memory)
clock.exs:1
{:reloaded, Clock, [Clock]}
Basic data types
# integer
1
# float
1.0
# atom
:name
# boolean - atom behind the scene
true || false
false == :false
# string: UTF-8 encoded binary
"hi"
# charlist: list of code points
'hi' == [104, 105]
# => true
# list
[1, 2, 3]
# tuple
{:ok, 1}
# map
%{a: 1, b: 2}
# anonymous function
square = fn(n) -> n * n end
square.(3)
# => 9
# PID
spawn(fn() -> 1 + 1 end)
# => #PID<0.132.0>
# Ref
spawn_monitor(fn() -> 1 + 1 end)
{#PID<0.90.0>, #Reference<0.919.270.234>}
Lists
- implemented as linked lists
# linked list syntax
[1] = [1 | []]
[1, 2] = [1 | [ 2 | []]]
[1, 2, 3] = [1 | [ 2 | [3 | []]]]
# concatenation
[1, 2] ++ [3, 4]
#=> [1, 2, 3, 4]
Maps
# map literal
user = %{first_name: "harry", last_name: "hole"}
# update field(s)
user = %{user | first_name: "marry"}
# => %{first_name: "marry", last_name: "hole"}
# dot access notation
user.first_name
# => "marry"
- key-value data structure
- no restriction on key type, anything can be a key
- dot notation can be used to access atom keys
Modules
defmodule Stack do
def new() do
[]
end
def push(stack, item) do
[item | stack]
end
def pop(stack) do
[_head | tail] = stack
tail
end
end
stack = Stack.new()
stack = Stack.push(stack, 1)
stack = Stack.push(stack, 2)
Stack.pop(stack)
# => [1]
Pipe operator
- passes result of the "left" expression as the first argument to the function on the "right" side
Stack.new()
|> Stack.push(1)
|> Stack.push(2)
|> Stack.pop()
# => [1]
Stack.pop(
Stack.push(
Stack.push(
Stack.new(),
1
),
2
)
)
Functions - clauses
defmodule Util do
def empty_array?([]) do
true
end
def empty_array?(_) do
false
end
end
- functions can have multiple clauses
- given set of arguments, elixir will try to invoke each one in
order they are declared until matching one is found, or raise an error
Functions - defaults
defmodule Util do
def inc(n, step \\ 1) do
n + step
end
end
- functions support default arguments
Functions - guards
defmodule Math do
def positive?(n) when (is_float(n) or is_integer(n)) and n > 0 do
true
end
def positive?(_n) do
false
end
end
- guards augment pattern matching by allowing more complex validation of input arguments
- guards allow only subset of expressions
Structures
defmodule User do
defstruct [:first_name, :last_name]
def with_first_name(user, name) do
%{user | first_name: name}
end
end
user = %User{first_name: "Harry", last_name: "Hole"}
# => %User{first_name: "Harry", last_name: "Hole"}
john = User.with_first_name(user, "John")
# => %User{first_name: "John", last_name: "Hole"}
john.first_name
# => "John"
Map.get(john, :first_name)
# => "John"
- underneath is a normal map
Match operator
- if posible, matches right side with the left side of the expression
- as consequence, assigns value to a variable
1 = 1
2 = 1
# => ** (MatchError) no match of right hand side value: 1
a = 1
a
# => 1
a = 2
a
# => 2
3 = a
# => ** (MatchError) no match of right hand side value: 2
Pattern matching
- ability to detect internals of the data structure
- allows binding value to a variable(destructuring)
# overloading
def divide(l, 0) do
:error
end
def divide(l, r) do
l / r
end
# destructuring
{:ok, %Goth.Token{token: token}} = token.for_scope(scope)
# branching
case do_something() do
{:success, result} ->
# ...
{:error, reason} ->
# ...
end
Concurrency
- concurent execution is achieved by running logic in multiple processes
- processes are created using Kernel.spawn
$> pid = spawn(fn() -> IO.puts "Hi" end)
Hi
#PID<0.340.0>
$> Process.alive?(pid)
$> false
Communication
- processes communicate via asynchronous messages
- arguments are deep copied
me = self()
pid = spawn(fn() ->
send(me, "hi")
end)
receive do
message ->
IO.inspect "Received: #{message}"
end
Links
- links create bi-directional dependency between processes
- crash in process will bring down all connected processes
- links are created via Kernel#spawn_link
iex(26)> spawn_link(fn() -> 1/0 end)
** (EXIT from #PID<0.96.0>) shell process exited with reason: an exception was raised:
** (ArithmeticError) bad argument in arithmetic expression
:erlang./(1, 0)
17:04:55.141 [error] Process #PID<0.141.0> raised an exception
** (ArithmeticError) bad argument in arithmetic expression
:erlang./(1, 0)
Interactive Elixir (1.6.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
Trapping exits
- when trapping exits, crashes in other processes(links) are converted to messages
iex(4)> Process.flag(:trap_exit, true)
true
iex(5)> spawn_link(fn() -> 1/0 end)
#PID<0.150.0>
17:06:06.437 [error] Process #PID<0.150.0> raised an exception
** (ArithmeticError) bad argument in arithmetic expression
:erlang./(1, 0)
iex(6)> flush
{:EXIT, #PID<0.150.0>, {:badarith, [{:erlang, :/, [1, 0], []}]}}
:ok
Monitors
- monitors create uni-directional connection between processes
- usefull when parent process wants to be notified about lifecycle events in children
iex(1)> spawn_monitor(fn() -> 1/0 end)
{#PID<0.98.0>, #Reference<0.2335281372.2063597571.182390>}
iex(2)>
17:20:34.543 [error] Process #PID<0.98.0> raised an exception
** (ArithmeticError) bad argument in arithmetic expression
:erlang./(1, 0)
nil
iex(3)> flush
{:DOWN, #Reference<0.2335281372.2063597571.182390>, :process, #PID<0.98.0>,
{:badarith, [{:erlang, :/, [1, 0], []}]}}
:ok
Wallet
defmodule Wallet do
defstruct [:uid, :transactions]
def new(uid) do
%Wallet{uid: uid, transactions: []}
end
def withdraw(wallet, amount) when amount > 0 do
%{wallet | transactions: [-amount | wallet.transactions]}
end
def deposit(wallet, amount) when amount > 0 do
%{wallet | transactions: [amount | wallet.transactions]}
end
def balance(wallet) do
Enum.sum(wallet.transactions)
end
def transactions(wallet) do
Enum.reverse(wallet.transactions)
end
end
Ruby vs Elixir
Learning curve
Syntax
Performance

Concurency


Ecosystem
Documentation

Ruby vs Elixir
Productivity
Maintainability
Debugging
Refactoring
Deployment
Testing
DSL

I'am the last slide
Questions?
Elixir
By vrabac
Elixir
- 532