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

  • 788