SlideShare a Scribd company logo
HELLO ELIXIR (AND OTP)
Abel Muiño (@amuino) & Rok Biderman (@RokBiderman)
ULTRA SHORT INTROS
Abel Muiño
Lead developer at Cabify. Works with ruby for a living.
abel.muino@cabify.com / @amuino
Rok Biderman
Senior Go developer at Cabify. Has an interesting past position.
Go ask him.
rok.biderman@cabify.com / @RokBiderman
WE ARE HIRING
Ruby, Go, Javascript, Android, iOS
(Just not for Elixir, yet)
HELLO ELIXIR (AND OTP)
Abel Muiño (@amuino) & Rok Biderman (@RokBiderman)
GOALS
➤ Show some code, this is a programming meet up
➤ Share our Elixir learning path
➤ Learn something from feedback and criticism
➤ Hopefully at least one other person will learn one thing
“This is not production code
-Abel Muiño
BUILDING AN
OCR MODULE
Extracting quotes from
memes
TL;DR
http://github.com/amuino/ocr
MIX NEW
$ mix new ocr
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/ocr.ex
* creating test
* creating test/test_helper.exs
* creating test/ocr_test.exs
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:
cd ocr
mix test
Run "mix help" for more commands.
MIX NEW
$ mix new ocr
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/ocr.ex
* creating test
* creating test/test_helper.exs
* creating test/ocr_test.exs
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:
cd ocr
mix test
Run "mix help" for more commands.
Project Definition
MIX NEW
$ mix new ocr
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/ocr.ex
* creating test
* creating test/test_helper.exs
* creating test/ocr_test.exs
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:
cd ocr
mix test
Run "mix help" for more commands.
Project Definition
App config
MIX NEW
$ mix new ocr
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/ocr.ex
* creating test
* creating test/test_helper.exs
* creating test/ocr_test.exs
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:
cd ocr
mix test
Run "mix help" for more commands.
Project Definition
App config
Main module
MIX NEW
$ mix new ocr
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/ocr.ex
* creating test
* creating test/test_helper.exs
* creating test/ocr_test.exs
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:
cd ocr
mix test
Run "mix help" for more commands.
Project Definition
App config
Main module
😓Not talking about tests today
THE INTERACTIVE SHELL
$ iex -S mix
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-
threads:10] [hipe] [kernel-poll:false] [dtrace]
Compiled lib/ocr.ex
Generated ocr app
Consolidated List.Chars
Consolidated Collectable
Consolidated String.Chars
Consolidated Enumerable
Consolidated IEx.Info
Consolidated Inspect
Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h()
ENTER for help)
iex(1)> Ocr
Ocr
THE INTERACTIVE SHELL
$ iex -S mix
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-
threads:10] [hipe] [kernel-poll:false] [dtrace]
Compiled lib/ocr.ex
Generated ocr app
Consolidated List.Chars
Consolidated Collectable
Consolidated String.Chars
Consolidated Enumerable
Consolidated IEx.Info
Consolidated Inspect
Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h()
ENTER for help)
iex(1)> Ocr
Ocr
Automatically
compiles new files
THE INTERACTIVE SHELL
$ iex -S mix
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-
threads:10] [hipe] [kernel-poll:false] [dtrace]
Compiled lib/ocr.ex
Generated ocr app
Consolidated List.Chars
Consolidated Collectable
Consolidated String.Chars
Consolidated Enumerable
Consolidated IEx.Info
Consolidated Inspect
Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h()
ENTER for help)
iex(1)> Ocr
Ocr
Automatically
compiles new files
Lots of first-run noise
THE INTERACTIVE SHELL
$ iex -S mix
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-
threads:10] [hipe] [kernel-poll:false] [dtrace]
Compiled lib/ocr.ex
Generated ocr app
Consolidated List.Chars
Consolidated Collectable
Consolidated String.Chars
Consolidated Enumerable
Consolidated IEx.Info
Consolidated Inspect
Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h()
ENTER for help)
iex(1)> Ocr
Ocr
Automatically
compiles new files
Lots of first-run noise
SUCCESS!
Our module exists
💡TIP: TRAINING WHEELS
➤ We are learning, so getting quick feedback is useful
➤ Credo is a static code analysis tool for the Elixir language with a
focus on teaching and code consistency.

https://github.com/rrrene/credo
➤ Credo installs as a project dependency
➤ Adds a new task to mix to analyse our code
➤ Excellent, very detailed, feedback
ADD A DEPENDENCY
Find the dependency info in hex.pm
Edit mix.exs
defp deps do
[
{:credo, "~> 0.3", only: [:dev]}
]
end
Install it locally
$ mix geps.get
ADD A DEPENDENCY
Find the dependency info in hex.pm
Edit mix.exs
defp deps do
[
{:credo, "~> 0.3", only: [:dev]}
]
end
Install it locally
$ mix geps.get
Dependencies are an
array of tuples.
ADD A DEPENDENCY
Find the dependency info in hex.pm
Edit mix.exs
defp deps do
[
{:credo, "~> 0.3", only: [:dev]}
]
end
Install it locally
$ mix geps.get
Dependencies are an
array of tuples.
Only installs the
dependency in the :dev
environment
TRY IT
$ mix credo
…
Code Readability
[R] ! Modules should have a @moduledoc tag.
lib/ocr.ex:1:11 (Ocr)
$ mix credo lib/ocr.ex:1:11
TRY IT
$ mix credo
…
Code Readability
[R] ! Modules should have a @moduledoc tag.
lib/ocr.ex:1:11 (Ocr)
$ mix credo lib/ocr.ex:1:11
OMG!
TRY IT
$ mix credo
…
Code Readability
[R] ! Modules should have a @moduledoc tag.
lib/ocr.ex:1:11 (Ocr)
$ mix credo lib/ocr.ex:1:11
OMG!
Detailed explanation on the
error, how to suppress it,
etc…
NOW WHAT?
➤ Use Google Vision API to perform the actual OCR
➤ Has no client in hex.pm
➤ It is a REST API → {:httpoison, "~> 0.8.3"}
➤ Returns JSON → {:poison, "~> 2.1.0"}
➤ Needs authentication → {:goth, "~> 0.1.2”}
➤ Build a nice façade
MIX.EXS
def application do
[applications: [:logger, :httpoison, :goth]]
end
defp deps do
[
{:httpoison, "~> 0.8.3"},
{:poison, "~> 2.1.0"},
{:goth, "~> 0.1.2"},
{:credo, "~> 0.3", only: [:dev]}
]
end
MIX.EXS
def application do
[applications: [:logger, :httpoison, :goth]]
end
defp deps do
[
{:httpoison, "~> 0.8.3"},
{:poison, "~> 2.1.0"},
{:goth, "~> 0.1.2"},
{:credo, "~> 0.3", only: [:dev]}
]
end
Some deps also need
their app to be started
CONFIG/CONFIG.EXS
use Mix.Config
config :goth,
json: "config/google-creds.json" |> File.read!
CONFIG/CONFIG.EXS
use Mix.Config
config :goth,
json: "config/google-creds.json" |> File.read!
Some deps also have
their own config
NOW WHAT?
➤ We will write 2 modules:
➤ Ocr.GoogleVision for the API client.
➤ Ocr for our façade
💡TIP: MODULE NAMES
➤ Convention:
➤ Ocr ! lib/ocr.ex
➤ Ocr.GoogleVision ! lib/ocr/google_vision.ex
➤ Modules names are just names. Dots in the name do not
represent any parent/child relationship.
LIB/OCR/GOOGLE_VISION.EX
defmodule Ocr.GoogleVision do
def extract_text(image64) do
image64 |> make_request |> read_body
end
# MAGIC!
end
LIB/OCR/GOOGLE_VISION.EX
defmodule Ocr.GoogleVision do
def extract_text(image64) do
image64 |> make_request |> read_body
end
# MAGIC!
end
base64
encoded
image
LIB/OCR/GOOGLE_VISION.EX
defmodule Ocr.GoogleVision do
def extract_text(image64) do
image64 |> make_request |> read_body
end
# MAGIC!
end
base64
encoded
image
send to
Google
LIB/OCR/GOOGLE_VISION.EX
defmodule Ocr.GoogleVision do
def extract_text(image64) do
image64 |> make_request |> read_body
end
# MAGIC!
end
base64
encoded
image
send to
Google
get the text
from the
response
LIB/OCR/GOOGLE_VISION.EX
@url "https://vision.googleapis.com/v1/images:annotate"
@feature_text_detection "TEXT_DETECTION"
@auth_scope "https://www.googleapis.com/auth/cloud-platform"
def make_request(image64) do
HTTPoison.post!(@url, payload(image64), headers)
end
defp payload(image64) do
%{requests: [
%{image: %{content: image64},
features: [%{type: @feature_text_detection}]}
]
} |> Poison.encode!
end
defp headers do
{:ok, token} = Goth.Token.for_scope(@auth_scope)
[{"Authorization", "#{token.type} #{token.token}"}]
end
LIB/OCR/GOOGLE_VISION.EX
@url "https://vision.googleapis.com/v1/images:annotate"
@feature_text_detection "TEXT_DETECTION"
@auth_scope "https://www.googleapis.com/auth/cloud-platform"
def make_request(image64) do
HTTPoison.post!(@url, payload(image64), headers)
end
defp payload(image64) do
%{requests: [
%{image: %{content: image64},
features: [%{type: @feature_text_detection}]}
]
} |> Poison.encode!
end
defp headers do
{:ok, token} = Goth.Token.for_scope(@auth_scope)
[{"Authorization", "#{token.type} #{token.token}"}]
end
Module attributes (used as a constants)
LIB/OCR/GOOGLE_VISION.EX
@url "https://vision.googleapis.com/v1/images:annotate"
@feature_text_detection "TEXT_DETECTION"
@auth_scope "https://www.googleapis.com/auth/cloud-platform"
def make_request(image64) do
HTTPoison.post!(@url, payload(image64), headers)
end
defp payload(image64) do
%{requests: [
%{image: %{content: image64},
features: [%{type: @feature_text_detection}]}
]
} |> Poison.encode!
end
defp headers do
{:ok, token} = Goth.Token.for_scope(@auth_scope)
[{"Authorization", "#{token.type} #{token.token}"}]
end
Module attributes (used as a constants)
HTTP POST some JSON to some URL
with some Headers
LIB/OCR/GOOGLE_VISION.EX
@url "https://vision.googleapis.com/v1/images:annotate"
@feature_text_detection "TEXT_DETECTION"
@auth_scope "https://www.googleapis.com/auth/cloud-platform"
def make_request(image64) do
HTTPoison.post!(@url, payload(image64), headers)
end
defp payload(image64) do
%{requests: [
%{image: %{content: image64},
features: [%{type: @feature_text_detection}]}
]
} |> Poison.encode!
end
defp headers do
{:ok, token} = Goth.Token.for_scope(@auth_scope)
[{"Authorization", "#{token.type} #{token.token}"}]
end
Module attributes (used as a constants)
HTTP POST some JSON to some URL
with some Headers
The JSON Google wants
LIB/OCR/GOOGLE_VISION.EX
@url "https://vision.googleapis.com/v1/images:annotate"
@feature_text_detection "TEXT_DETECTION"
@auth_scope "https://www.googleapis.com/auth/cloud-platform"
def make_request(image64) do
HTTPoison.post!(@url, payload(image64), headers)
end
defp payload(image64) do
%{requests: [
%{image: %{content: image64},
features: [%{type: @feature_text_detection}]}
]
} |> Poison.encode!
end
defp headers do
{:ok, token} = Goth.Token.for_scope(@auth_scope)
[{"Authorization", "#{token.type} #{token.token}"}]
end
Module attributes (used as a constants)
HTTP POST some JSON to some URL
with some Headers
The JSON Google wants
Get a token
LIB/OCR/GOOGLE_VISION.EX
@url "https://vision.googleapis.com/v1/images:annotate"
@feature_text_detection "TEXT_DETECTION"
@auth_scope "https://www.googleapis.com/auth/cloud-platform"
def make_request(image64) do
HTTPoison.post!(@url, payload(image64), headers)
end
defp payload(image64) do
%{requests: [
%{image: %{content: image64},
features: [%{type: @feature_text_detection}]}
]
} |> Poison.encode!
end
defp headers do
{:ok, token} = Goth.Token.for_scope(@auth_scope)
[{"Authorization", "#{token.type} #{token.token}"}]
end
Module attributes (used as a constants)
HTTP POST some JSON to some URL
with some Headers
The JSON Google wants
Get a token
Put the token on the request headers
LIB/OCR/GOOGLE_VISION.EX
def read_body(%HTTPoison.Response{body: body, status_code: 200})
do
body |>
Poison.decode! |>
get_in(["responses", &first/3, "textAnnotations",
&first/3, "description"])
end
defp first(:get, nil, _), do: nil
defp first(:get, data, next) do
data |> List.first |> next.()
end
LIB/OCR/GOOGLE_VISION.EX
def read_body(%HTTPoison.Response{body: body, status_code: 200})
do
body |>
Poison.decode! |>
get_in(["responses", &first/3, "textAnnotations",
&first/3, "description"])
end
defp first(:get, nil, _), do: nil
defp first(:get, data, next) do
data |> List.first |> next.()
end
Only care about body and success http status
LIB/OCR/GOOGLE_VISION.EX
def read_body(%HTTPoison.Response{body: body, status_code: 200})
do
body |>
Poison.decode! |>
get_in(["responses", &first/3, "textAnnotations",
&first/3, "description"])
end
defp first(:get, nil, _), do: nil
defp first(:get, data, next) do
data |> List.first |> next.()
end
Only care about body and success http status
Parse JSON response
LIB/OCR/GOOGLE_VISION.EX
def read_body(%HTTPoison.Response{body: body, status_code: 200})
do
body |>
Poison.decode! |>
get_in(["responses", &first/3, "textAnnotations",
&first/3, "description"])
end
defp first(:get, nil, _), do: nil
defp first(:get, data, next) do
data |> List.first |> next.()
end
Only care about body and success http status
Parse JSON response
Extract the text
LIB/OCR/GOOGLE_VISION.EX
def read_body(%HTTPoison.Response{body: body, status_code: 200})
do
body |>
Poison.decode! |>
get_in(["responses", &first/3, "textAnnotations",
&first/3, "description"])
end
defp first(:get, nil, _), do: nil
defp first(:get, data, next) do
data |> List.first |> next.()
end
Only care about body and success http status
Parse JSON response
Extract the text
Custom lookup functions
LIB/OCR/GOOGLE_VISION.EX
def read_body(%HTTPoison.Response{body: body, status_code: 200})
do
body |>
Poison.decode! |>
get_in(["responses", &first/3, "textAnnotations",
&first/3, "description"])
end
defp first(:get, nil, _), do: nil
defp first(:get, data, next) do
data |> List.first |> next.()
end
Only care about body and success http status
Parse JSON response
Extract the text
Custom lookup functions
funky syntax to invoke an
anonymous function
💡TIP: GET_IN IS AWESOME
➤ Navigates nested structures (maps)
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> get_in(users, ["john", :age])
27
➤ Returns nil on missing keys
iex> get_in(users, ["unknown", :age])
nil
➤ Accepts functions for navigating other types
➤ Elixir 1.3 will have common functions predefined for tuples
and lists
➤ Access.at(0) will replace my custom first (PR #4719)
➤ Also check get_and_update_in, put_in, update_in
LIB/OCR.EX
defmodule Ocr do
def from_base64(b64), do: Ocr.GoogleVision.extract_text(b64)
def from_image(image_data) do
image_data |> Base.encode64 |> from_base64
end
def from_path(path), do: path |> File.read! |> from_image
def from_url(url), do: HTTPoison.get!(url).body |> from_image
end
LIB/OCR.EX
defmodule Ocr do
def from_base64(b64), do: Ocr.GoogleVision.extract_text(b64)
def from_image(image_data) do
image_data |> Base.encode64 |> from_base64
end
def from_path(path), do: path |> File.read! |> from_image
def from_url(url), do: HTTPoison.get!(url).body |> from_image
end
LIB/OCR.EX
defmodule Ocr do
def from_base64(b64), do: Ocr.GoogleVision.extract_text(b64)
def from_image(image_data) do
image_data |> Base.encode64 |> from_base64
end
def from_path(path), do: path |> File.read! |> from_image
def from_url(url), do: HTTPoison.get!(url).body |> from_image
end
LIB/OCR.EX
defmodule Ocr do
def from_base64(b64), do: Ocr.GoogleVision.extract_text(b64)
def from_image(image_data) do
image_data |> Base.encode64 |> from_base64
end
def from_path(path), do: path |> File.read! |> from_image
def from_url(url), do: HTTPoison.get!(url).body |> from_image
end
FUN!
$ iex -S mix
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:
10] [hipe] [kernel-poll:false] [dtrace]
A new Hex version is available (0.12.0), please update with `mix
local.hex`
Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for
help)
iex(1)> meme_url = "http://ih0.redbubble.net/image.16611809.2383/fc,
550x550,black.jpg"
"http://ih0.redbubble.net/image.16611809.2383/fc,550x550,black.jpg"
iex(2)> IO.puts Ocr.from_url meme_url
GETS ELIKIR PR ACCEPTED
I SAID WHO WANTS TO
FUCKING TOUCH ME?
Suranyami
:ok
FUN!
$ iex -S mix
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:
10] [hipe] [kernel-poll:false] [dtrace]
A new Hex version is available (0.12.0), please update with `mix
local.hex`
Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for
help)
iex(1)> meme_url = "http://ih0.redbubble.net/image.16611809.2383/fc,
550x550,black.jpg"
"http://ih0.redbubble.net/image.16611809.2383/fc,550x550,black.jpg"
iex(2)> IO.puts Ocr.from_url meme_url
GETS ELIKIR PR ACCEPTED
I SAID WHO WANTS TO
FUCKING TOUCH ME?
Suranyami
:ok
STATEFUL? STATELESS?
Cast your vote!
ANSWER: STATEFUL
➤ Auth tokens are not requested every time
➤ Requested on first use
➤ Refreshed on the background when about to expire
➤ Goth.TokenStore is a GenServer
➤ Just one of the predefined behaviours to make easier to
work with processes
➤ Starts a process with some state
➤ Receives messages and updates the state
➤ There is more state (like Goth.Config)
DEPS/GOTH/LIB/GOTH/TOKEN_STORE.EX
defmodule Goth.TokenStore do
use Xenserver
alias Goth.Token
def start_link do
GenServer.start_link(__MODULE__, %{}, [name: __MODULE__])
end
def handle_call({:store, scope, token}, _from, state) do
pid_or_timer = Token.queue_for_refresh(token)
{:reply, pid_or_timer, Map.put(state, scope, token)}
end
def handle_call({:find, scope}, _from, state) do
{:reply, Map.fetch(state, scope), state}
end
end
DEPS/GOTH/LIB/GOTH/TOKEN_STORE.EX
defmodule Goth.TokenStore do
use Xenserver
alias Goth.Token
def start_link do
GenServer.start_link(__MODULE__, %{}, [name: __MODULE__])
end
def handle_call({:store, scope, token}, _from, state) do
pid_or_timer = Token.queue_for_refresh(token)
{:reply, pid_or_timer, Map.put(state, scope, token)}
end
def handle_call({:find, scope}, _from, state) do
{:reply, Map.fetch(state, scope), state}
end
end
Start the process,
DEPS/GOTH/LIB/GOTH/TOKEN_STORE.EX
defmodule Goth.TokenStore do
use Xenserver
alias Goth.Token
def start_link do
GenServer.start_link(__MODULE__, %{}, [name: __MODULE__])
end
def handle_call({:store, scope, token}, _from, state) do
pid_or_timer = Token.queue_for_refresh(token)
{:reply, pid_or_timer, Map.put(state, scope, token)}
end
def handle_call({:find, scope}, _from, state) do
{:reply, Map.fetch(state, scope), state}
end
end
Start the process, Initial state is an empty map
DEPS/GOTH/LIB/GOTH/TOKEN_STORE.EX
defmodule Goth.TokenStore do
use Xenserver
alias Goth.Token
def start_link do
GenServer.start_link(__MODULE__, %{}, [name: __MODULE__])
end
def handle_call({:store, scope, token}, _from, state) do
pid_or_timer = Token.queue_for_refresh(token)
{:reply, pid_or_timer, Map.put(state, scope, token)}
end
def handle_call({:find, scope}, _from, state) do
{:reply, Map.fetch(state, scope), state}
end
end
Start the process, Initial state is an empty map
The process has a name
DEPS/GOTH/LIB/GOTH/TOKEN_STORE.EX
defmodule Goth.TokenStore do
use Xenserver
alias Goth.Token
def start_link do
GenServer.start_link(__MODULE__, %{}, [name: __MODULE__])
end
def handle_call({:store, scope, token}, _from, state) do
pid_or_timer = Token.queue_for_refresh(token)
{:reply, pid_or_timer, Map.put(state, scope, token)}
end
def handle_call({:find, scope}, _from, state) do
{:reply, Map.fetch(state, scope), state}
end
end
Start the process, Initial state is an empty map
The process has a name
Handle 2 types of messages, returning something
FUN WITH TOKEN STORE
$ iex -S mix
iex(1)> token = %Goth.Token{token: "FAKE",
expires: :os.system_time + 10_000_000}
%Goth.Token{expires: 1464647952951907000, scope: nil,
token: "FAKE", type: nil}
iex(2)> GenServer.call Goth.TokenStore, {:find, "Elixir"}
:error
iex(3)> GenServer.call Goth.TokenStore, {:store, "Elixir",
token}
{:ok, {1464647950910798703208727, #Reference<0.0.7.228>}}
iex(4)> GenServer.call Goth.TokenStore, {:find, "Elixir"}
{:ok,
%Goth.Token{expires: 1464647952951907000, scope: nil,
token: “FAKE", type: nil}}
FUN WITH TOKEN STORE
$ iex -S mix
iex(1)> token = %Goth.Token{token: "FAKE",
expires: :os.system_time + 10_000_000}
%Goth.Token{expires: 1464647952951907000, scope: nil,
token: "FAKE", type: nil}
iex(2)> GenServer.call Goth.TokenStore, {:find, "Elixir"}
:error
iex(3)> GenServer.call Goth.TokenStore, {:store, "Elixir",
token}
{:ok, {1464647950910798703208727, #Reference<0.0.7.228>}}
iex(4)> GenServer.call Goth.TokenStore, {:find, "Elixir"}
{:ok,
%Goth.Token{expires: 1464647952951907000, scope: nil,
token: “FAKE", type: nil}}
The process name
FUN WITH TOKEN STORE
$ iex -S mix
iex(1)> token = %Goth.Token{token: "FAKE",
expires: :os.system_time + 10_000_000}
%Goth.Token{expires: 1464647952951907000, scope: nil,
token: "FAKE", type: nil}
iex(2)> GenServer.call Goth.TokenStore, {:find, "Elixir"}
:error
iex(3)> GenServer.call Goth.TokenStore, {:store, "Elixir",
token}
{:ok, {1464647950910798703208727, #Reference<0.0.7.228>}}
iex(4)> GenServer.call Goth.TokenStore, {:find, "Elixir"}
{:ok,
%Goth.Token{expires: 1464647952951907000, scope: nil,
token: “FAKE", type: nil}}
The process name
The message
DEPS/GOTH/LIB/GOTH/TOKEN_STORE.EX
defmodule Goth.TokenStore do
def store(%Token{}=token), do: store(token.scope, token)
def store(scopes, %Token{} = token) do
GenServer.call(__MODULE__, {:store, scopes, token})
end
def find(scope) do
GenServer.call(__MODULE__, {:find, scope})
end
end
DEPS/GOTH/LIB/GOTH/TOKEN_STORE.EX
defmodule Goth.TokenStore do
def store(%Token{}=token), do: store(token.scope, token)
def store(scopes, %Token{} = token) do
GenServer.call(__MODULE__, {:store, scopes, token})
end
def find(scope) do
GenServer.call(__MODULE__, {:find, scope})
end
end
Provide a client API (usually in the same module)
with nicer methods hiding the use of
Genserver.call
LET’S TALK ABOUT OTP
Processes, state, concurrency, supervisors,… oh my!
RECAP
➤ Erlang is:
➤ general-purpose
➤ concurrent
➤ garbage-collected
➤ programming language
➤ and runtime system
➤ Elixir:
➤ Builds on top of all that
START A PROCESS
➤ Basic concurrency primitive
➤ Simplest way to create, use spawn with a function
defmodule BasicMessagePassing.Call do
def concat(a, b) do
IO.puts("#{a} #{b}")
end
end
iex(2)> BasicMessagePassing.Call.concat "Elixir", "Madrid"
Elixir Madrid
:ok
iex(3)> spawn BasicMessagePassing.Call, :concat, ["Elixir", "Madrid"]
Elixir Madrid
#PID<0.69.0>
START A PROCESS
➤ Basic concurrency primitive
➤ Simplest way to create, use spawn with a function
defmodule BasicMessagePassing.Call do
def concat(a, b) do
IO.puts("#{a} #{b}")
end
end
iex(2)> BasicMessagePassing.Call.concat "Elixir", "Madrid"
Elixir Madrid
:ok
iex(3)> spawn BasicMessagePassing.Call, :concat, ["Elixir", "Madrid"]
Elixir Madrid
#PID<0.69.0>
Same process
START A PROCESS
➤ Basic concurrency primitive
➤ Simplest way to create, use spawn with a function
defmodule BasicMessagePassing.Call do
def concat(a, b) do
IO.puts("#{a} #{b}")
end
end
iex(2)> BasicMessagePassing.Call.concat "Elixir", "Madrid"
Elixir Madrid
:ok
iex(3)> spawn BasicMessagePassing.Call, :concat, ["Elixir", "Madrid"]
Elixir Madrid
#PID<0.69.0>
Same process
Spawned process id
LISTEN FOR MESSAGES
defmodule BasicMessagePassing.Listen do
def listen do
receive do
{:ok, input} -> IO.puts "#{input} Madrid"
end
end
end
iex(5)> pid = spawn(BasicMessagePassing.Listen, :listen, [])
#PID<0.82.0>
iex(6)> send pid, {:ok, "Elixir"}
Elixir Madrid
{:ok, "Elixir"}
iex(8)> Process.alive? pid
false
FIBONACCI TIME!
defmodule FibSerial do
def calculate(ns) do
ns
|> Enum.map(&(calc(&1)))
|> inspect
|> IO.puts
end
def calc(n) do
calc(n, 1, 0)
end
defp calc(0, _, _) do
0
end
defp calc(1, a, b) do
a + b
end
defp calc(n, a, b) do
calc(n - 1, b, a + b)
end
end
FibSerial.calculate(Enum.to_list(1..10000))
FIBONACCI TIME!
defmodule FibSerial do
def calculate(ns) do
ns
|> Enum.map(&(calc(&1)))
|> inspect
|> IO.puts
end
def calc(n) do
calc(n, 1, 0)
end
defp calc(0, _, _) do
0
end
defp calc(1, a, b) do
a + b
end
defp calc(n, a, b) do
calc(n - 1, b, a + b)
end
end
FibSerial.calculate(Enum.to_list(1..10000)) About 6 seconds
PARALLEL FIBONACCI TIME!
defmodule FibParallel do
def calculate(ns) do
ns
|> Enum.with_index
|> Enum.map(fn(ni) -> spawn FibParallel, :send_calc, [self, ni] end)
listen(length(ns), [])
end
def send_calc(pid, {n, i}) do
send pid, {calc(n), i}
end
defp listen(lns, result) do
receive do
fib ->
result = [fib | result]
if lns == 1 do
result
|> Enum.sort(fn({_, a}, {_, b}) -> a < b end)
|> Enum.map(fn({f, _}) -> f end)
|> inspect
|> IO.puts
else
listen(lns - 1, result)
end
end
end
end
FibSerial.calculate(Enum.to_list(1..10000))
PARALLEL FIBONACCI TIME!
defmodule FibParallel do
def calculate(ns) do
ns
|> Enum.with_index
|> Enum.map(fn(ni) -> spawn FibParallel, :send_calc, [self, ni] end)
listen(length(ns), [])
end
def send_calc(pid, {n, i}) do
send pid, {calc(n), i}
end
defp listen(lns, result) do
receive do
fib ->
result = [fib | result]
if lns == 1 do
result
|> Enum.sort(fn({_, a}, {_, b}) -> a < b end)
|> Enum.map(fn({f, _}) -> f end)
|> inspect
|> IO.puts
else
listen(lns - 1, result)
end
end
end
end
FibSerial.calculate(Enum.to_list(1..10000)) About 2 seconds (4 cores)
LINKING PROCESSES
➤ If child dies, parent dies
defmodule BasicMessagePassing.Linking do
def exit, do: exit(:crash)
def start do
spawn_link(BasicMessagePassing.Linking, :exit, [])
receive do
{:done} -> IO.puts "no more waiting"
end
end
end
iex(13)> BasicMessagePassing.Linking.start
** (EXIT from #PID<0.57.0>) :crash
Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for
help)
iex(1)>
LINKING PROCESSES
➤ If child dies, parent dies
defmodule BasicMessagePassing.Linking do
def exit, do: exit(:crash)
def start do
spawn_link(BasicMessagePassing.Linking, :exit, [])
receive do
{:done} -> IO.puts "no more waiting"
end
end
end
iex(13)> BasicMessagePassing.Linking.start
** (EXIT from #PID<0.57.0>) :crash
Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for
help)
iex(1)>
iex died! and was restarted
LINKING PROCESSES
➤ If child dies, parent dies… unless it handles the dead
defmodule BasicMessagePassing.Linking do
def exit, do: exit(:crash)
def start do
Process.flag(:trap_exit, true)
spawn_link(BasicMessagePassing.Linking, :exit, [])
receive do
{:EXIT, from_pid, reason} -> IO.puts "#{inspect(self)} is
aware #{inspect(from_pid)} exited because of #{reason}"
end
end
end
iex(24)> BasicMessagePassing.Linking.start
#PID<0.57.0> is aware #PID<0.157.0> exited because of crash
:ok
LINKING PROCESSES
➤ If child dies, parent dies… unless it handles the dead
defmodule BasicMessagePassing.Linking do
def exit, do: exit(:crash)
def start do
Process.flag(:trap_exit, true)
spawn_link(BasicMessagePassing.Linking, :exit, [])
receive do
{:EXIT, from_pid, reason} -> IO.puts "#{inspect(self)} is
aware #{inspect(from_pid)} exited because of #{reason}"
end
end
end
iex(24)> BasicMessagePassing.Linking.start
#PID<0.57.0> is aware #PID<0.157.0> exited because of crash
:ok
survive children
LINKING PROCESSES
➤ If child dies, parent dies… unless it handles the dead
defmodule BasicMessagePassing.Linking do
def exit, do: exit(:crash)
def start do
Process.flag(:trap_exit, true)
spawn_link(BasicMessagePassing.Linking, :exit, [])
receive do
{:EXIT, from_pid, reason} -> IO.puts "#{inspect(self)} is
aware #{inspect(from_pid)} exited because of #{reason}"
end
end
end
iex(24)> BasicMessagePassing.Linking.start
#PID<0.57.0> is aware #PID<0.157.0> exited because of crash
:ok
survive children
handle deads
GENSERVER
➤ Simplifies all this stuff
➤ Is a process like any other Elixir process
➤ Standard set of interface functions, tracing and error reporting
➤ call: request with response
➤ cast: request without response
A STACK
defmodule Stack do
use GenServer
def start_link(state, opts  []) do
GenServer.start_link(__MODULE__, state, opts)
end
def handle_call(:pop, _from, [h|t]) do
{:reply, h, t}
end
def handle_cast({:push, h}, t) do
{:noreply, [h|t]}
end
end
A SUPERVISED STACK
iex(7)> import Supervisor.Spec
nil
iex(8)> children = [
...(8)> worker(Stack, [[:first], [name: :stack_name]])
...(8)> ]
[{Stack, {Stack, :start_link, [[:first], [name: :stack_name]]}, :permanent,
5000, :worker, [Stack]}]
iex(9)> {:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
{:ok, #PID<0.247.0>}
iex(10)> GenServer.call(:stack_name, :pop)
:first
iex(11)> GenServer.call(:stack_name, :pop)
18:04:35.012 [error] GenServer :stack_name terminating
** (FunctionClauseError) no function clause matching in Stack.handle_call/3
iex:13: Stack.handle_call(:pop, {#PID<0.135.0>, #Reference<0.0.1.317>}, [])
[..]
Last message: :pop
State: []
(elixir) lib/gen_server.ex:564: GenServer.call/3
iex(11)> GenServer.call(:stack_name, :pop)
:first
SUPERVISOR FLAVORS
➤ one_for_one: dead worker is replaced by another one
➤ rest_for_all: after one dies, all others have to be restarted
➤ rest_for_one: all workers started after this one will be
restarted
➤ simple_one_for_one: for dynamically attached children,
Supervisor is required to contain only one child
QUESTIONS?
THANKS!
Abel Muiño (@amuino) & Rok Biderman (@RokBiderman)

More Related Content

PDF
Bootstrap |> Elixir - Easy fun for busy developers
PDF
Introduction to Elixir
PDF
Elixir Into Production
PDF
Elixir and OTP
PDF
Phoenix for Rails Devs
PDF
Concurrency in Elixir with OTP
PDF
ElixirConf Lightning Talk: Elixir |> Production
PDF
Elixir
Bootstrap |> Elixir - Easy fun for busy developers
Introduction to Elixir
Elixir Into Production
Elixir and OTP
Phoenix for Rails Devs
Concurrency in Elixir with OTP
ElixirConf Lightning Talk: Elixir |> Production
Elixir

What's hot (20)

PDF
Learning Elixir as a Rubyist
PDF
Actor Clustering with Docker Containers and Akka.Net in F#
PPTX
Akka.net versus microsoft orleans
PDF
Erlang and Elixir
PDF
Elixir and Phoenix for Rubyists
PDF
Atmosphere 2014
PDF
Concurrecny inf sharp
PDF
Introduction to Ansible (Pycon7 2016)
PDF
Intro to React
PDF
Cocoapods and Most common used library in Swift
KEY
Wider than rails
PDF
Mini Rails Framework
PDF
Phoenix Framework
PDF
Designing net-aws-glacier
PDF
KKBOX WWDC17 Xcode debug - Oliver
PDF
infra-as-code
PDF
effective_r27
PDF
Automating the Cloud with Terraform, and Ansible
PDF
Tame cloud complexity with F# powered DSLs (build stuff)
PDF
Kotlin 1.2: Sharing code between platforms
Learning Elixir as a Rubyist
Actor Clustering with Docker Containers and Akka.Net in F#
Akka.net versus microsoft orleans
Erlang and Elixir
Elixir and Phoenix for Rubyists
Atmosphere 2014
Concurrecny inf sharp
Introduction to Ansible (Pycon7 2016)
Intro to React
Cocoapods and Most common used library in Swift
Wider than rails
Mini Rails Framework
Phoenix Framework
Designing net-aws-glacier
KKBOX WWDC17 Xcode debug - Oliver
infra-as-code
effective_r27
Automating the Cloud with Terraform, and Ansible
Tame cloud complexity with F# powered DSLs (build stuff)
Kotlin 1.2: Sharing code between platforms
Ad

Viewers also liked (20)

PDF
Elixir intro
PDF
How Elixir helped us scale our Video User Profile Service for the Olympics
ODP
Elixir basics-2
PDF
Elixir – Peeking into Elixir's Processes, OTP and Supervisors
PDF
Mad scalability: Scaling when you are not Google
PDF
Intro to elixir and phoenix
PDF
Elixir - Easy fun for busy developers @ Devoxx 2016
PDF
Brief Intro to Phoenix - Elixir Meetup at BukaLapak
PDF
Flow-based programming with Elixir
PDF
Elixir & Phoenix 推坑
PDF
Build Your Own Real-Time Web Service with Elixir Phoenix
PDF
Learn Elixir at Manchester Lambda Lounge
PDF
ITB2016 - Mixing up the front end with ColdBox elixir
PDF
Maven 3… so what?
PPTX
BioContainers on ELIXIR All Hands 2017
PDF
Clojure made-simple - John Stevenson
PDF
What can be done with Java, but should better be done with Erlang (@pavlobaron)
PDF
Messaging With Erlang And Jabber
PDF
High Performance Erlang
KEY
Winning the Erlang Edit•Build•Test Cycle
Elixir intro
How Elixir helped us scale our Video User Profile Service for the Olympics
Elixir basics-2
Elixir – Peeking into Elixir's Processes, OTP and Supervisors
Mad scalability: Scaling when you are not Google
Intro to elixir and phoenix
Elixir - Easy fun for busy developers @ Devoxx 2016
Brief Intro to Phoenix - Elixir Meetup at BukaLapak
Flow-based programming with Elixir
Elixir & Phoenix 推坑
Build Your Own Real-Time Web Service with Elixir Phoenix
Learn Elixir at Manchester Lambda Lounge
ITB2016 - Mixing up the front end with ColdBox elixir
Maven 3… so what?
BioContainers on ELIXIR All Hands 2017
Clojure made-simple - John Stevenson
What can be done with Java, but should better be done with Erlang (@pavlobaron)
Messaging With Erlang And Jabber
High Performance Erlang
Winning the Erlang Edit•Build•Test Cycle
Ad

Similar to Hello elixir (and otp) (20)

PDF
Introducing Elixir and OTP at the Erlang BASH
PDF
Intro to Elixir talk
PDF
ElixirConf 2017 - Writing an Editor in Elixir - Ian Duggan
PDF
Elixir in a nutshell - Ecosystem (session 1)
PPTX
Adopting Elixir in a 10 year old codebase
PPTX
Repeating History...On Purpose...with Elixir
PDF
Elixir and Dialyzer, Types and Typespecs, using and understanding them
PDF
Elixir and elm
PDF
Delivering High Quality Elixir Code using Gitlab
PDF
Elixir cheatsheet
PPTX
PDF
Elixir for aspiring Erlang developers
PDF
Elixir Berlin 2019: Dominic Letz on Doing Blockchain with Elixir
PDF
外傷的Elixir
PDF
Elixir Programming Language 101
PDF
Introduction to Erlang/(Elixir) at a Webilea Hands-On Session
PDF
Origins of Elixir programming language
PDF
Introduction to Elixir
PDF
Elixir - GDG - Nantes
PPTX
Children of Ruby
Introducing Elixir and OTP at the Erlang BASH
Intro to Elixir talk
ElixirConf 2017 - Writing an Editor in Elixir - Ian Duggan
Elixir in a nutshell - Ecosystem (session 1)
Adopting Elixir in a 10 year old codebase
Repeating History...On Purpose...with Elixir
Elixir and Dialyzer, Types and Typespecs, using and understanding them
Elixir and elm
Delivering High Quality Elixir Code using Gitlab
Elixir cheatsheet
Elixir for aspiring Erlang developers
Elixir Berlin 2019: Dominic Letz on Doing Blockchain with Elixir
外傷的Elixir
Elixir Programming Language 101
Introduction to Erlang/(Elixir) at a Webilea Hands-On Session
Origins of Elixir programming language
Introduction to Elixir
Elixir - GDG - Nantes
Children of Ruby

Recently uploaded (20)

PPTX
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
PPTX
Essential Infomation Tech presentation.pptx
PDF
Understanding Forklifts - TECH EHS Solution
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 41
PPTX
ai tools demonstartion for schools and inter college
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PDF
Nekopoi APK 2025 free lastest update
PDF
Navsoft: AI-Powered Business Solutions & Custom Software Development
PDF
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
PDF
top salesforce developer skills in 2025.pdf
PDF
2025 Textile ERP Trends: SAP, Odoo & Oracle
PPTX
Reimagine Home Health with the Power of Agentic AI​
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PPTX
Operating system designcfffgfgggggggvggggggggg
PDF
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
PDF
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
Essential Infomation Tech presentation.pptx
Understanding Forklifts - TECH EHS Solution
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
Upgrade and Innovation Strategies for SAP ERP Customers
Internet Downloader Manager (IDM) Crack 6.42 Build 41
ai tools demonstartion for schools and inter college
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
Nekopoi APK 2025 free lastest update
Navsoft: AI-Powered Business Solutions & Custom Software Development
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
top salesforce developer skills in 2025.pdf
2025 Textile ERP Trends: SAP, Odoo & Oracle
Reimagine Home Health with the Power of Agentic AI​
Design an Analysis of Algorithms II-SECS-1021-03
Operating system designcfffgfgggggggvggggggggg
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
How to Migrate SBCGlobal Email to Yahoo Easily
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus

Hello elixir (and otp)

  • 1. HELLO ELIXIR (AND OTP) Abel Muiño (@amuino) & Rok Biderman (@RokBiderman)
  • 2. ULTRA SHORT INTROS Abel Muiño Lead developer at Cabify. Works with ruby for a living. abel.muino@cabify.com / @amuino Rok Biderman Senior Go developer at Cabify. Has an interesting past position. Go ask him. rok.biderman@cabify.com / @RokBiderman
  • 3. WE ARE HIRING Ruby, Go, Javascript, Android, iOS (Just not for Elixir, yet)
  • 4. HELLO ELIXIR (AND OTP) Abel Muiño (@amuino) & Rok Biderman (@RokBiderman)
  • 5. GOALS ➤ Show some code, this is a programming meet up ➤ Share our Elixir learning path ➤ Learn something from feedback and criticism ➤ Hopefully at least one other person will learn one thing
  • 6. “This is not production code -Abel Muiño
  • 9. MIX NEW $ mix new ocr * creating README.md * creating .gitignore * creating mix.exs * creating config * creating config/config.exs * creating lib * creating lib/ocr.ex * creating test * creating test/test_helper.exs * creating test/ocr_test.exs Your Mix project was created successfully. You can use "mix" to compile it, test it, and more: cd ocr mix test Run "mix help" for more commands.
  • 10. MIX NEW $ mix new ocr * creating README.md * creating .gitignore * creating mix.exs * creating config * creating config/config.exs * creating lib * creating lib/ocr.ex * creating test * creating test/test_helper.exs * creating test/ocr_test.exs Your Mix project was created successfully. You can use "mix" to compile it, test it, and more: cd ocr mix test Run "mix help" for more commands. Project Definition
  • 11. MIX NEW $ mix new ocr * creating README.md * creating .gitignore * creating mix.exs * creating config * creating config/config.exs * creating lib * creating lib/ocr.ex * creating test * creating test/test_helper.exs * creating test/ocr_test.exs Your Mix project was created successfully. You can use "mix" to compile it, test it, and more: cd ocr mix test Run "mix help" for more commands. Project Definition App config
  • 12. MIX NEW $ mix new ocr * creating README.md * creating .gitignore * creating mix.exs * creating config * creating config/config.exs * creating lib * creating lib/ocr.ex * creating test * creating test/test_helper.exs * creating test/ocr_test.exs Your Mix project was created successfully. You can use "mix" to compile it, test it, and more: cd ocr mix test Run "mix help" for more commands. Project Definition App config Main module
  • 13. MIX NEW $ mix new ocr * creating README.md * creating .gitignore * creating mix.exs * creating config * creating config/config.exs * creating lib * creating lib/ocr.ex * creating test * creating test/test_helper.exs * creating test/ocr_test.exs Your Mix project was created successfully. You can use "mix" to compile it, test it, and more: cd ocr mix test Run "mix help" for more commands. Project Definition App config Main module 😓Not talking about tests today
  • 14. THE INTERACTIVE SHELL $ iex -S mix Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async- threads:10] [hipe] [kernel-poll:false] [dtrace] Compiled lib/ocr.ex Generated ocr app Consolidated List.Chars Consolidated Collectable Consolidated String.Chars Consolidated Enumerable Consolidated IEx.Info Consolidated Inspect Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> Ocr Ocr
  • 15. THE INTERACTIVE SHELL $ iex -S mix Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async- threads:10] [hipe] [kernel-poll:false] [dtrace] Compiled lib/ocr.ex Generated ocr app Consolidated List.Chars Consolidated Collectable Consolidated String.Chars Consolidated Enumerable Consolidated IEx.Info Consolidated Inspect Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> Ocr Ocr Automatically compiles new files
  • 16. THE INTERACTIVE SHELL $ iex -S mix Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async- threads:10] [hipe] [kernel-poll:false] [dtrace] Compiled lib/ocr.ex Generated ocr app Consolidated List.Chars Consolidated Collectable Consolidated String.Chars Consolidated Enumerable Consolidated IEx.Info Consolidated Inspect Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> Ocr Ocr Automatically compiles new files Lots of first-run noise
  • 17. THE INTERACTIVE SHELL $ iex -S mix Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async- threads:10] [hipe] [kernel-poll:false] [dtrace] Compiled lib/ocr.ex Generated ocr app Consolidated List.Chars Consolidated Collectable Consolidated String.Chars Consolidated Enumerable Consolidated IEx.Info Consolidated Inspect Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> Ocr Ocr Automatically compiles new files Lots of first-run noise SUCCESS! Our module exists
  • 18. 💡TIP: TRAINING WHEELS ➤ We are learning, so getting quick feedback is useful ➤ Credo is a static code analysis tool for the Elixir language with a focus on teaching and code consistency.
 https://github.com/rrrene/credo ➤ Credo installs as a project dependency ➤ Adds a new task to mix to analyse our code ➤ Excellent, very detailed, feedback
  • 19. ADD A DEPENDENCY Find the dependency info in hex.pm Edit mix.exs defp deps do [ {:credo, "~> 0.3", only: [:dev]} ] end Install it locally $ mix geps.get
  • 20. ADD A DEPENDENCY Find the dependency info in hex.pm Edit mix.exs defp deps do [ {:credo, "~> 0.3", only: [:dev]} ] end Install it locally $ mix geps.get Dependencies are an array of tuples.
  • 21. ADD A DEPENDENCY Find the dependency info in hex.pm Edit mix.exs defp deps do [ {:credo, "~> 0.3", only: [:dev]} ] end Install it locally $ mix geps.get Dependencies are an array of tuples. Only installs the dependency in the :dev environment
  • 22. TRY IT $ mix credo … Code Readability [R] ! Modules should have a @moduledoc tag. lib/ocr.ex:1:11 (Ocr) $ mix credo lib/ocr.ex:1:11
  • 23. TRY IT $ mix credo … Code Readability [R] ! Modules should have a @moduledoc tag. lib/ocr.ex:1:11 (Ocr) $ mix credo lib/ocr.ex:1:11 OMG!
  • 24. TRY IT $ mix credo … Code Readability [R] ! Modules should have a @moduledoc tag. lib/ocr.ex:1:11 (Ocr) $ mix credo lib/ocr.ex:1:11 OMG! Detailed explanation on the error, how to suppress it, etc…
  • 25. NOW WHAT? ➤ Use Google Vision API to perform the actual OCR ➤ Has no client in hex.pm ➤ It is a REST API → {:httpoison, "~> 0.8.3"} ➤ Returns JSON → {:poison, "~> 2.1.0"} ➤ Needs authentication → {:goth, "~> 0.1.2”} ➤ Build a nice façade
  • 26. MIX.EXS def application do [applications: [:logger, :httpoison, :goth]] end defp deps do [ {:httpoison, "~> 0.8.3"}, {:poison, "~> 2.1.0"}, {:goth, "~> 0.1.2"}, {:credo, "~> 0.3", only: [:dev]} ] end
  • 27. MIX.EXS def application do [applications: [:logger, :httpoison, :goth]] end defp deps do [ {:httpoison, "~> 0.8.3"}, {:poison, "~> 2.1.0"}, {:goth, "~> 0.1.2"}, {:credo, "~> 0.3", only: [:dev]} ] end Some deps also need their app to be started
  • 28. CONFIG/CONFIG.EXS use Mix.Config config :goth, json: "config/google-creds.json" |> File.read!
  • 29. CONFIG/CONFIG.EXS use Mix.Config config :goth, json: "config/google-creds.json" |> File.read! Some deps also have their own config
  • 30. NOW WHAT? ➤ We will write 2 modules: ➤ Ocr.GoogleVision for the API client. ➤ Ocr for our façade
  • 31. 💡TIP: MODULE NAMES ➤ Convention: ➤ Ocr ! lib/ocr.ex ➤ Ocr.GoogleVision ! lib/ocr/google_vision.ex ➤ Modules names are just names. Dots in the name do not represent any parent/child relationship.
  • 32. LIB/OCR/GOOGLE_VISION.EX defmodule Ocr.GoogleVision do def extract_text(image64) do image64 |> make_request |> read_body end # MAGIC! end
  • 33. LIB/OCR/GOOGLE_VISION.EX defmodule Ocr.GoogleVision do def extract_text(image64) do image64 |> make_request |> read_body end # MAGIC! end base64 encoded image
  • 34. LIB/OCR/GOOGLE_VISION.EX defmodule Ocr.GoogleVision do def extract_text(image64) do image64 |> make_request |> read_body end # MAGIC! end base64 encoded image send to Google
  • 35. LIB/OCR/GOOGLE_VISION.EX defmodule Ocr.GoogleVision do def extract_text(image64) do image64 |> make_request |> read_body end # MAGIC! end base64 encoded image send to Google get the text from the response
  • 36. LIB/OCR/GOOGLE_VISION.EX @url "https://vision.googleapis.com/v1/images:annotate" @feature_text_detection "TEXT_DETECTION" @auth_scope "https://www.googleapis.com/auth/cloud-platform" def make_request(image64) do HTTPoison.post!(@url, payload(image64), headers) end defp payload(image64) do %{requests: [ %{image: %{content: image64}, features: [%{type: @feature_text_detection}]} ] } |> Poison.encode! end defp headers do {:ok, token} = Goth.Token.for_scope(@auth_scope) [{"Authorization", "#{token.type} #{token.token}"}] end
  • 37. LIB/OCR/GOOGLE_VISION.EX @url "https://vision.googleapis.com/v1/images:annotate" @feature_text_detection "TEXT_DETECTION" @auth_scope "https://www.googleapis.com/auth/cloud-platform" def make_request(image64) do HTTPoison.post!(@url, payload(image64), headers) end defp payload(image64) do %{requests: [ %{image: %{content: image64}, features: [%{type: @feature_text_detection}]} ] } |> Poison.encode! end defp headers do {:ok, token} = Goth.Token.for_scope(@auth_scope) [{"Authorization", "#{token.type} #{token.token}"}] end Module attributes (used as a constants)
  • 38. LIB/OCR/GOOGLE_VISION.EX @url "https://vision.googleapis.com/v1/images:annotate" @feature_text_detection "TEXT_DETECTION" @auth_scope "https://www.googleapis.com/auth/cloud-platform" def make_request(image64) do HTTPoison.post!(@url, payload(image64), headers) end defp payload(image64) do %{requests: [ %{image: %{content: image64}, features: [%{type: @feature_text_detection}]} ] } |> Poison.encode! end defp headers do {:ok, token} = Goth.Token.for_scope(@auth_scope) [{"Authorization", "#{token.type} #{token.token}"}] end Module attributes (used as a constants) HTTP POST some JSON to some URL with some Headers
  • 39. LIB/OCR/GOOGLE_VISION.EX @url "https://vision.googleapis.com/v1/images:annotate" @feature_text_detection "TEXT_DETECTION" @auth_scope "https://www.googleapis.com/auth/cloud-platform" def make_request(image64) do HTTPoison.post!(@url, payload(image64), headers) end defp payload(image64) do %{requests: [ %{image: %{content: image64}, features: [%{type: @feature_text_detection}]} ] } |> Poison.encode! end defp headers do {:ok, token} = Goth.Token.for_scope(@auth_scope) [{"Authorization", "#{token.type} #{token.token}"}] end Module attributes (used as a constants) HTTP POST some JSON to some URL with some Headers The JSON Google wants
  • 40. LIB/OCR/GOOGLE_VISION.EX @url "https://vision.googleapis.com/v1/images:annotate" @feature_text_detection "TEXT_DETECTION" @auth_scope "https://www.googleapis.com/auth/cloud-platform" def make_request(image64) do HTTPoison.post!(@url, payload(image64), headers) end defp payload(image64) do %{requests: [ %{image: %{content: image64}, features: [%{type: @feature_text_detection}]} ] } |> Poison.encode! end defp headers do {:ok, token} = Goth.Token.for_scope(@auth_scope) [{"Authorization", "#{token.type} #{token.token}"}] end Module attributes (used as a constants) HTTP POST some JSON to some URL with some Headers The JSON Google wants Get a token
  • 41. LIB/OCR/GOOGLE_VISION.EX @url "https://vision.googleapis.com/v1/images:annotate" @feature_text_detection "TEXT_DETECTION" @auth_scope "https://www.googleapis.com/auth/cloud-platform" def make_request(image64) do HTTPoison.post!(@url, payload(image64), headers) end defp payload(image64) do %{requests: [ %{image: %{content: image64}, features: [%{type: @feature_text_detection}]} ] } |> Poison.encode! end defp headers do {:ok, token} = Goth.Token.for_scope(@auth_scope) [{"Authorization", "#{token.type} #{token.token}"}] end Module attributes (used as a constants) HTTP POST some JSON to some URL with some Headers The JSON Google wants Get a token Put the token on the request headers
  • 42. LIB/OCR/GOOGLE_VISION.EX def read_body(%HTTPoison.Response{body: body, status_code: 200}) do body |> Poison.decode! |> get_in(["responses", &first/3, "textAnnotations", &first/3, "description"]) end defp first(:get, nil, _), do: nil defp first(:get, data, next) do data |> List.first |> next.() end
  • 43. LIB/OCR/GOOGLE_VISION.EX def read_body(%HTTPoison.Response{body: body, status_code: 200}) do body |> Poison.decode! |> get_in(["responses", &first/3, "textAnnotations", &first/3, "description"]) end defp first(:get, nil, _), do: nil defp first(:get, data, next) do data |> List.first |> next.() end Only care about body and success http status
  • 44. LIB/OCR/GOOGLE_VISION.EX def read_body(%HTTPoison.Response{body: body, status_code: 200}) do body |> Poison.decode! |> get_in(["responses", &first/3, "textAnnotations", &first/3, "description"]) end defp first(:get, nil, _), do: nil defp first(:get, data, next) do data |> List.first |> next.() end Only care about body and success http status Parse JSON response
  • 45. LIB/OCR/GOOGLE_VISION.EX def read_body(%HTTPoison.Response{body: body, status_code: 200}) do body |> Poison.decode! |> get_in(["responses", &first/3, "textAnnotations", &first/3, "description"]) end defp first(:get, nil, _), do: nil defp first(:get, data, next) do data |> List.first |> next.() end Only care about body and success http status Parse JSON response Extract the text
  • 46. LIB/OCR/GOOGLE_VISION.EX def read_body(%HTTPoison.Response{body: body, status_code: 200}) do body |> Poison.decode! |> get_in(["responses", &first/3, "textAnnotations", &first/3, "description"]) end defp first(:get, nil, _), do: nil defp first(:get, data, next) do data |> List.first |> next.() end Only care about body and success http status Parse JSON response Extract the text Custom lookup functions
  • 47. LIB/OCR/GOOGLE_VISION.EX def read_body(%HTTPoison.Response{body: body, status_code: 200}) do body |> Poison.decode! |> get_in(["responses", &first/3, "textAnnotations", &first/3, "description"]) end defp first(:get, nil, _), do: nil defp first(:get, data, next) do data |> List.first |> next.() end Only care about body and success http status Parse JSON response Extract the text Custom lookup functions funky syntax to invoke an anonymous function
  • 48. 💡TIP: GET_IN IS AWESOME ➤ Navigates nested structures (maps) iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}} iex> get_in(users, ["john", :age]) 27 ➤ Returns nil on missing keys iex> get_in(users, ["unknown", :age]) nil ➤ Accepts functions for navigating other types ➤ Elixir 1.3 will have common functions predefined for tuples and lists ➤ Access.at(0) will replace my custom first (PR #4719) ➤ Also check get_and_update_in, put_in, update_in
  • 49. LIB/OCR.EX defmodule Ocr do def from_base64(b64), do: Ocr.GoogleVision.extract_text(b64) def from_image(image_data) do image_data |> Base.encode64 |> from_base64 end def from_path(path), do: path |> File.read! |> from_image def from_url(url), do: HTTPoison.get!(url).body |> from_image end
  • 50. LIB/OCR.EX defmodule Ocr do def from_base64(b64), do: Ocr.GoogleVision.extract_text(b64) def from_image(image_data) do image_data |> Base.encode64 |> from_base64 end def from_path(path), do: path |> File.read! |> from_image def from_url(url), do: HTTPoison.get!(url).body |> from_image end
  • 51. LIB/OCR.EX defmodule Ocr do def from_base64(b64), do: Ocr.GoogleVision.extract_text(b64) def from_image(image_data) do image_data |> Base.encode64 |> from_base64 end def from_path(path), do: path |> File.read! |> from_image def from_url(url), do: HTTPoison.get!(url).body |> from_image end
  • 52. LIB/OCR.EX defmodule Ocr do def from_base64(b64), do: Ocr.GoogleVision.extract_text(b64) def from_image(image_data) do image_data |> Base.encode64 |> from_base64 end def from_path(path), do: path |> File.read! |> from_image def from_url(url), do: HTTPoison.get!(url).body |> from_image end
  • 53. FUN! $ iex -S mix Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads: 10] [hipe] [kernel-poll:false] [dtrace] A new Hex version is available (0.12.0), please update with `mix local.hex` Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> meme_url = "http://ih0.redbubble.net/image.16611809.2383/fc, 550x550,black.jpg" "http://ih0.redbubble.net/image.16611809.2383/fc,550x550,black.jpg" iex(2)> IO.puts Ocr.from_url meme_url GETS ELIKIR PR ACCEPTED I SAID WHO WANTS TO FUCKING TOUCH ME? Suranyami :ok
  • 54. FUN! $ iex -S mix Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads: 10] [hipe] [kernel-poll:false] [dtrace] A new Hex version is available (0.12.0), please update with `mix local.hex` Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> meme_url = "http://ih0.redbubble.net/image.16611809.2383/fc, 550x550,black.jpg" "http://ih0.redbubble.net/image.16611809.2383/fc,550x550,black.jpg" iex(2)> IO.puts Ocr.from_url meme_url GETS ELIKIR PR ACCEPTED I SAID WHO WANTS TO FUCKING TOUCH ME? Suranyami :ok
  • 56. ANSWER: STATEFUL ➤ Auth tokens are not requested every time ➤ Requested on first use ➤ Refreshed on the background when about to expire ➤ Goth.TokenStore is a GenServer ➤ Just one of the predefined behaviours to make easier to work with processes ➤ Starts a process with some state ➤ Receives messages and updates the state ➤ There is more state (like Goth.Config)
  • 57. DEPS/GOTH/LIB/GOTH/TOKEN_STORE.EX defmodule Goth.TokenStore do use Xenserver alias Goth.Token def start_link do GenServer.start_link(__MODULE__, %{}, [name: __MODULE__]) end def handle_call({:store, scope, token}, _from, state) do pid_or_timer = Token.queue_for_refresh(token) {:reply, pid_or_timer, Map.put(state, scope, token)} end def handle_call({:find, scope}, _from, state) do {:reply, Map.fetch(state, scope), state} end end
  • 58. DEPS/GOTH/LIB/GOTH/TOKEN_STORE.EX defmodule Goth.TokenStore do use Xenserver alias Goth.Token def start_link do GenServer.start_link(__MODULE__, %{}, [name: __MODULE__]) end def handle_call({:store, scope, token}, _from, state) do pid_or_timer = Token.queue_for_refresh(token) {:reply, pid_or_timer, Map.put(state, scope, token)} end def handle_call({:find, scope}, _from, state) do {:reply, Map.fetch(state, scope), state} end end Start the process,
  • 59. DEPS/GOTH/LIB/GOTH/TOKEN_STORE.EX defmodule Goth.TokenStore do use Xenserver alias Goth.Token def start_link do GenServer.start_link(__MODULE__, %{}, [name: __MODULE__]) end def handle_call({:store, scope, token}, _from, state) do pid_or_timer = Token.queue_for_refresh(token) {:reply, pid_or_timer, Map.put(state, scope, token)} end def handle_call({:find, scope}, _from, state) do {:reply, Map.fetch(state, scope), state} end end Start the process, Initial state is an empty map
  • 60. DEPS/GOTH/LIB/GOTH/TOKEN_STORE.EX defmodule Goth.TokenStore do use Xenserver alias Goth.Token def start_link do GenServer.start_link(__MODULE__, %{}, [name: __MODULE__]) end def handle_call({:store, scope, token}, _from, state) do pid_or_timer = Token.queue_for_refresh(token) {:reply, pid_or_timer, Map.put(state, scope, token)} end def handle_call({:find, scope}, _from, state) do {:reply, Map.fetch(state, scope), state} end end Start the process, Initial state is an empty map The process has a name
  • 61. DEPS/GOTH/LIB/GOTH/TOKEN_STORE.EX defmodule Goth.TokenStore do use Xenserver alias Goth.Token def start_link do GenServer.start_link(__MODULE__, %{}, [name: __MODULE__]) end def handle_call({:store, scope, token}, _from, state) do pid_or_timer = Token.queue_for_refresh(token) {:reply, pid_or_timer, Map.put(state, scope, token)} end def handle_call({:find, scope}, _from, state) do {:reply, Map.fetch(state, scope), state} end end Start the process, Initial state is an empty map The process has a name Handle 2 types of messages, returning something
  • 62. FUN WITH TOKEN STORE $ iex -S mix iex(1)> token = %Goth.Token{token: "FAKE", expires: :os.system_time + 10_000_000} %Goth.Token{expires: 1464647952951907000, scope: nil, token: "FAKE", type: nil} iex(2)> GenServer.call Goth.TokenStore, {:find, "Elixir"} :error iex(3)> GenServer.call Goth.TokenStore, {:store, "Elixir", token} {:ok, {1464647950910798703208727, #Reference<0.0.7.228>}} iex(4)> GenServer.call Goth.TokenStore, {:find, "Elixir"} {:ok, %Goth.Token{expires: 1464647952951907000, scope: nil, token: “FAKE", type: nil}}
  • 63. FUN WITH TOKEN STORE $ iex -S mix iex(1)> token = %Goth.Token{token: "FAKE", expires: :os.system_time + 10_000_000} %Goth.Token{expires: 1464647952951907000, scope: nil, token: "FAKE", type: nil} iex(2)> GenServer.call Goth.TokenStore, {:find, "Elixir"} :error iex(3)> GenServer.call Goth.TokenStore, {:store, "Elixir", token} {:ok, {1464647950910798703208727, #Reference<0.0.7.228>}} iex(4)> GenServer.call Goth.TokenStore, {:find, "Elixir"} {:ok, %Goth.Token{expires: 1464647952951907000, scope: nil, token: “FAKE", type: nil}} The process name
  • 64. FUN WITH TOKEN STORE $ iex -S mix iex(1)> token = %Goth.Token{token: "FAKE", expires: :os.system_time + 10_000_000} %Goth.Token{expires: 1464647952951907000, scope: nil, token: "FAKE", type: nil} iex(2)> GenServer.call Goth.TokenStore, {:find, "Elixir"} :error iex(3)> GenServer.call Goth.TokenStore, {:store, "Elixir", token} {:ok, {1464647950910798703208727, #Reference<0.0.7.228>}} iex(4)> GenServer.call Goth.TokenStore, {:find, "Elixir"} {:ok, %Goth.Token{expires: 1464647952951907000, scope: nil, token: “FAKE", type: nil}} The process name The message
  • 65. DEPS/GOTH/LIB/GOTH/TOKEN_STORE.EX defmodule Goth.TokenStore do def store(%Token{}=token), do: store(token.scope, token) def store(scopes, %Token{} = token) do GenServer.call(__MODULE__, {:store, scopes, token}) end def find(scope) do GenServer.call(__MODULE__, {:find, scope}) end end
  • 66. DEPS/GOTH/LIB/GOTH/TOKEN_STORE.EX defmodule Goth.TokenStore do def store(%Token{}=token), do: store(token.scope, token) def store(scopes, %Token{} = token) do GenServer.call(__MODULE__, {:store, scopes, token}) end def find(scope) do GenServer.call(__MODULE__, {:find, scope}) end end Provide a client API (usually in the same module) with nicer methods hiding the use of Genserver.call
  • 67. LET’S TALK ABOUT OTP Processes, state, concurrency, supervisors,… oh my!
  • 68. RECAP ➤ Erlang is: ➤ general-purpose ➤ concurrent ➤ garbage-collected ➤ programming language ➤ and runtime system ➤ Elixir: ➤ Builds on top of all that
  • 69. START A PROCESS ➤ Basic concurrency primitive ➤ Simplest way to create, use spawn with a function defmodule BasicMessagePassing.Call do def concat(a, b) do IO.puts("#{a} #{b}") end end iex(2)> BasicMessagePassing.Call.concat "Elixir", "Madrid" Elixir Madrid :ok iex(3)> spawn BasicMessagePassing.Call, :concat, ["Elixir", "Madrid"] Elixir Madrid #PID<0.69.0>
  • 70. START A PROCESS ➤ Basic concurrency primitive ➤ Simplest way to create, use spawn with a function defmodule BasicMessagePassing.Call do def concat(a, b) do IO.puts("#{a} #{b}") end end iex(2)> BasicMessagePassing.Call.concat "Elixir", "Madrid" Elixir Madrid :ok iex(3)> spawn BasicMessagePassing.Call, :concat, ["Elixir", "Madrid"] Elixir Madrid #PID<0.69.0> Same process
  • 71. START A PROCESS ➤ Basic concurrency primitive ➤ Simplest way to create, use spawn with a function defmodule BasicMessagePassing.Call do def concat(a, b) do IO.puts("#{a} #{b}") end end iex(2)> BasicMessagePassing.Call.concat "Elixir", "Madrid" Elixir Madrid :ok iex(3)> spawn BasicMessagePassing.Call, :concat, ["Elixir", "Madrid"] Elixir Madrid #PID<0.69.0> Same process Spawned process id
  • 72. LISTEN FOR MESSAGES defmodule BasicMessagePassing.Listen do def listen do receive do {:ok, input} -> IO.puts "#{input} Madrid" end end end iex(5)> pid = spawn(BasicMessagePassing.Listen, :listen, []) #PID<0.82.0> iex(6)> send pid, {:ok, "Elixir"} Elixir Madrid {:ok, "Elixir"} iex(8)> Process.alive? pid false
  • 73. FIBONACCI TIME! defmodule FibSerial do def calculate(ns) do ns |> Enum.map(&(calc(&1))) |> inspect |> IO.puts end def calc(n) do calc(n, 1, 0) end defp calc(0, _, _) do 0 end defp calc(1, a, b) do a + b end defp calc(n, a, b) do calc(n - 1, b, a + b) end end FibSerial.calculate(Enum.to_list(1..10000))
  • 74. FIBONACCI TIME! defmodule FibSerial do def calculate(ns) do ns |> Enum.map(&(calc(&1))) |> inspect |> IO.puts end def calc(n) do calc(n, 1, 0) end defp calc(0, _, _) do 0 end defp calc(1, a, b) do a + b end defp calc(n, a, b) do calc(n - 1, b, a + b) end end FibSerial.calculate(Enum.to_list(1..10000)) About 6 seconds
  • 75. PARALLEL FIBONACCI TIME! defmodule FibParallel do def calculate(ns) do ns |> Enum.with_index |> Enum.map(fn(ni) -> spawn FibParallel, :send_calc, [self, ni] end) listen(length(ns), []) end def send_calc(pid, {n, i}) do send pid, {calc(n), i} end defp listen(lns, result) do receive do fib -> result = [fib | result] if lns == 1 do result |> Enum.sort(fn({_, a}, {_, b}) -> a < b end) |> Enum.map(fn({f, _}) -> f end) |> inspect |> IO.puts else listen(lns - 1, result) end end end end FibSerial.calculate(Enum.to_list(1..10000))
  • 76. PARALLEL FIBONACCI TIME! defmodule FibParallel do def calculate(ns) do ns |> Enum.with_index |> Enum.map(fn(ni) -> spawn FibParallel, :send_calc, [self, ni] end) listen(length(ns), []) end def send_calc(pid, {n, i}) do send pid, {calc(n), i} end defp listen(lns, result) do receive do fib -> result = [fib | result] if lns == 1 do result |> Enum.sort(fn({_, a}, {_, b}) -> a < b end) |> Enum.map(fn({f, _}) -> f end) |> inspect |> IO.puts else listen(lns - 1, result) end end end end FibSerial.calculate(Enum.to_list(1..10000)) About 2 seconds (4 cores)
  • 77. LINKING PROCESSES ➤ If child dies, parent dies defmodule BasicMessagePassing.Linking do def exit, do: exit(:crash) def start do spawn_link(BasicMessagePassing.Linking, :exit, []) receive do {:done} -> IO.puts "no more waiting" end end end iex(13)> BasicMessagePassing.Linking.start ** (EXIT from #PID<0.57.0>) :crash Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help) iex(1)>
  • 78. LINKING PROCESSES ➤ If child dies, parent dies defmodule BasicMessagePassing.Linking do def exit, do: exit(:crash) def start do spawn_link(BasicMessagePassing.Linking, :exit, []) receive do {:done} -> IO.puts "no more waiting" end end end iex(13)> BasicMessagePassing.Linking.start ** (EXIT from #PID<0.57.0>) :crash Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> iex died! and was restarted
  • 79. LINKING PROCESSES ➤ If child dies, parent dies… unless it handles the dead defmodule BasicMessagePassing.Linking do def exit, do: exit(:crash) def start do Process.flag(:trap_exit, true) spawn_link(BasicMessagePassing.Linking, :exit, []) receive do {:EXIT, from_pid, reason} -> IO.puts "#{inspect(self)} is aware #{inspect(from_pid)} exited because of #{reason}" end end end iex(24)> BasicMessagePassing.Linking.start #PID<0.57.0> is aware #PID<0.157.0> exited because of crash :ok
  • 80. LINKING PROCESSES ➤ If child dies, parent dies… unless it handles the dead defmodule BasicMessagePassing.Linking do def exit, do: exit(:crash) def start do Process.flag(:trap_exit, true) spawn_link(BasicMessagePassing.Linking, :exit, []) receive do {:EXIT, from_pid, reason} -> IO.puts "#{inspect(self)} is aware #{inspect(from_pid)} exited because of #{reason}" end end end iex(24)> BasicMessagePassing.Linking.start #PID<0.57.0> is aware #PID<0.157.0> exited because of crash :ok survive children
  • 81. LINKING PROCESSES ➤ If child dies, parent dies… unless it handles the dead defmodule BasicMessagePassing.Linking do def exit, do: exit(:crash) def start do Process.flag(:trap_exit, true) spawn_link(BasicMessagePassing.Linking, :exit, []) receive do {:EXIT, from_pid, reason} -> IO.puts "#{inspect(self)} is aware #{inspect(from_pid)} exited because of #{reason}" end end end iex(24)> BasicMessagePassing.Linking.start #PID<0.57.0> is aware #PID<0.157.0> exited because of crash :ok survive children handle deads
  • 82. GENSERVER ➤ Simplifies all this stuff ➤ Is a process like any other Elixir process ➤ Standard set of interface functions, tracing and error reporting ➤ call: request with response ➤ cast: request without response
  • 83. A STACK defmodule Stack do use GenServer def start_link(state, opts []) do GenServer.start_link(__MODULE__, state, opts) end def handle_call(:pop, _from, [h|t]) do {:reply, h, t} end def handle_cast({:push, h}, t) do {:noreply, [h|t]} end end
  • 84. A SUPERVISED STACK iex(7)> import Supervisor.Spec nil iex(8)> children = [ ...(8)> worker(Stack, [[:first], [name: :stack_name]]) ...(8)> ] [{Stack, {Stack, :start_link, [[:first], [name: :stack_name]]}, :permanent, 5000, :worker, [Stack]}] iex(9)> {:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one) {:ok, #PID<0.247.0>} iex(10)> GenServer.call(:stack_name, :pop) :first iex(11)> GenServer.call(:stack_name, :pop) 18:04:35.012 [error] GenServer :stack_name terminating ** (FunctionClauseError) no function clause matching in Stack.handle_call/3 iex:13: Stack.handle_call(:pop, {#PID<0.135.0>, #Reference<0.0.1.317>}, []) [..] Last message: :pop State: [] (elixir) lib/gen_server.ex:564: GenServer.call/3 iex(11)> GenServer.call(:stack_name, :pop) :first
  • 85. SUPERVISOR FLAVORS ➤ one_for_one: dead worker is replaced by another one ➤ rest_for_all: after one dies, all others have to be restarted ➤ rest_for_one: all workers started after this one will be restarted ➤ simple_one_for_one: for dynamically attached children, Supervisor is required to contain only one child
  • 87. THANKS! Abel Muiño (@amuino) & Rok Biderman (@RokBiderman)