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
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
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
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
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.
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
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
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
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