Going functional with
Elixir & OTP
Leszek Zalewski
Agenda
-
Overview & history
-
Language basics
-
Erlang VM
-
OTP
-
Extras
-
Summary
Overview & history
1980s - Ericsson develops Erlang, with concurrency and error recovery built into the language
1998 - Ericsson open sources Erlang
2011 - First appearance of Elixir
Elixir
Built on top of Erlang VM, so it's:
Functional
Immutable
Shared nothing concurrency
Distributed
Fault tolerant
Modern, approachable syntax
Types
Functions
Pattern matching
Recursion
Objects? Nope - Processes
Data structures
Language basics
Enumerables and Streams
Language basics
iex> 1 # integer
iex> 0x1F # integer
iex> 1.0 # float
iex> true # boolean
iex> :atom # atom / symbol
iex> "elixir" # string
iex> <<104, 101, 108, 108, 111, 0>> # binary
iex> fn a, b -> a + b end # function
Types
iex> [1, 2, 3] # list
iex> {1, 2, 3} # tuple (fixed size array)
iex> %{"foo" => "bar", "baz" => 2} # hash map
# Modules & Structs
defmodule Person do
defstruct name: "", age: nil
end
iex> %Person{name: "John", age: 67}
Language basics
Data Structures
iex> x = "foo"
"foo"
iex> "foo" = x # WAT!?
"foo"
iex> x = "bar"
"bar"
iex> ^x = "baz"
** (MatchError) no match of right hand side value: "baz"
iex> [color] = ["red"]
["red"]
iex> color
"red"
Language basics
Pattern matching
case call_api() do
# pattern match against Response struct with status code 200,
# extract body from the struct
{:ok, %Response{status_code: 200, body: body}} ->
{:ok, body}
# pattern match against Response struct with status code 404
# return normalized error
{:ok, %Response{status_code: 404}} ->
{:error, :not_found}
# catch all
msg ->
msg |> inspect() |> Logger.error()
{:error, :panic}
end
Language basics
Pattern matching
defmodule Person do
defstruct name: "", age: nil
def introduce(%Person{name: name, age: age}) do
"Hi, my name is #{name} and I'm #{age} years old"
end
end
iex> john = %Person{name: "John", age: 67}
%Person{name: "John", age: 67}
iex> Person.introduce(john)
"Hi, my name is John and I'm 67 years old"
Language basics
Functions (& pattern matching)
defmodule Person do
defstruct age: nil
def complain(%Person{age: age}) when is_number(age) and age > 60 do
"Im too old for this crud"
end
def complain(_) do
"Not again!"
end
end
iex> Person.complain(%Person{age: 67})
"I'm too old for this crud"
iex> Person.complain(%Person{age: 50})
"Not again!"
Language basics
Functions guard clauses
defmodule Recursion do
# pattern matching against empty list
def print_list([]) do
end
# pattern matching against head and tail
def print_list([head | tail]) do
head |> inspect |> IO.puts
print_list(tail)
end
end
iex> Recursion.print_list([1, 2, 3])
1
2
3
nil
Language basics
Recursion
# Eager `Enum` module
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]
Language basics
Enumerables - Enum
# each operation is going to generate
# an intermediate list until we reach the result
#
1..100_000
|> Enum.map(&(&1 * 3)) # fn x -> x * 3 end
|> Enum.filter(&(rem(&1, 2) != 0)) # fn x -> rem(x, 2) != 0 end
|> Enum.sum
7500000000
Language basics
Enumerables - Stream
# operations are delayed till final result is requested
#
1..100_000
|> Stream.map(&(&1 * 3)) # fn x -> x * 3 end
|> Stream.filter(&(rem(&1, 2) != 0)) # fn x -> rem(x, 2) != 0 end
|> Enum.sum
7500000000
spawn(fn ->
IO.puts("neat!")
end)
Language basics
Processes, links and monitors
spawn_link(fn ->
raise("Ops!")
end)
pid = spawn(fn ->
:timer.sleep(10_000)
raise("Ops!")
end)
Process.monitor(pid)
Basic process, when it crashes, parent process stays alive
Linked process, when it crashes, parent process dies as well
Monitored process, when crashed,
the parent process will be notified about it
DEMO
Erlang Virtual Machine
Processes implementation
Mailbox
Memory / State
Computation
Process
Lightweight compared to processes and threads in other languages
Communication happens only via message passing
Each process have it's own memory stack, not accessible from outside
Can process only one message from mailbox at a time
Erlang Virtual Machine
Preemptive multitasking
Reliably guarantees each process a regular "slice" of computing time
By default occupies all available cores
Erlang build-in tooling
Generic Servers
Gradual type system
Supervision Trees
Nodes clustering
In-memory DBs
Observer
Hot code upgrades
Erlang build-in tooling
Generic Servers
Construct / framework on top of processes and message passing
A server/client approach
Synchronous and asynchronous messaging
Demo
Erlang build-in tooling
Supervision Trees
supervisor
workers
(supervisor is worker as well)
when worker process dies
supervisor gets notified
worker gets restarted
Erlang build-in tooling
Observer
DEMO
Elixir build-in tooling
Hex package manager
DocTests - based on examples from documentation
Consistent code bases across community with code formatter
First class documentation
Concurrent tests by design
Elixir extras
Crucial packages are maintained by elixir core developers
Phoenix web framework, with focus on event driven architecture
Nerves project - IoT made easy
Flow - Apache Spark alike data processing
Summary
Takes best things from both world - static and dynamic
Build in building blocks for easy creation of decoupled and easy to understand systems
Great performance
Fast development
Great community
Questions?
Going functional with Elixir & OTP
By Leszek Zalewski
Going functional with Elixir & OTP
- 904