Hello elixir (and otp)
-
Upload
abel-muino -
Category
Software
-
view
585 -
download
0
Transcript of Hello elixir (and otp)
![Page 1: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/1.jpg)
HELLO ELIXIR (AND OTP)Abel Muiño (@amuino) & Rok Biderman (@RokBiderman)
![Page 2: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/2.jpg)
ULTRA SHORT INTROS
Abel Muiño Lead developer at Cabify. Works with ruby for a living.
[email protected] / @amuino
Rok Biderman Senior Go developer at Cabify. Has an interesting past position. Go ask him.
[email protected] / @RokBiderman
![Page 3: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/3.jpg)
WE ARE HIRINGRuby, Go, Javascript, Android, iOS
(Just not for Elixir, yet)
![Page 4: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/4.jpg)
HELLO ELIXIR (AND OTP)Abel Muiño (@amuino) & Rok Biderman (@RokBiderman)
![Page 5: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/5.jpg)
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
![Page 6: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/6.jpg)
“This is not production code
-Abel Muiño
![Page 7: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/7.jpg)
BUILDING AN OCR MODULE
Extracting quotes from memes
![Page 9: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/9.jpg)
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.
![Page 10: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/10.jpg)
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
![Page 11: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/11.jpg)
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
![Page 12: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/12.jpg)
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
![Page 13: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/13.jpg)
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
![Page 14: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/14.jpg)
THE INTERACTIVE SHELL
$ iex -S mixErlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Compiled lib/ocr.exGenerated ocr appConsolidated List.CharsConsolidated CollectableConsolidated String.CharsConsolidated EnumerableConsolidated IEx.InfoConsolidated InspectInteractive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help)iex(1)> OcrOcr
![Page 15: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/15.jpg)
THE INTERACTIVE SHELL
$ iex -S mixErlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Compiled lib/ocr.exGenerated ocr appConsolidated List.CharsConsolidated CollectableConsolidated String.CharsConsolidated EnumerableConsolidated IEx.InfoConsolidated InspectInteractive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help)iex(1)> OcrOcr
Automatically compiles new files
![Page 16: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/16.jpg)
THE INTERACTIVE SHELL
$ iex -S mixErlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Compiled lib/ocr.exGenerated ocr appConsolidated List.CharsConsolidated CollectableConsolidated String.CharsConsolidated EnumerableConsolidated IEx.InfoConsolidated InspectInteractive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help)iex(1)> OcrOcr
Automatically compiles new files
Lots of first-run noise
![Page 17: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/17.jpg)
THE INTERACTIVE SHELL
$ iex -S mixErlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
Compiled lib/ocr.exGenerated ocr appConsolidated List.CharsConsolidated CollectableConsolidated String.CharsConsolidated EnumerableConsolidated IEx.InfoConsolidated InspectInteractive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help)iex(1)> OcrOcr
Automatically compiles new files
Lots of first-run noise
SUCCESS! Our module exists
![Page 18: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/18.jpg)
💡 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
![Page 19: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/19.jpg)
ADD A DEPENDENCY
Find the dependency info in hex.pmEdit mix.exs defp deps do [ {:credo, "~> 0.3", only: [:dev]} ] end
Install it locally$ mix geps.get
![Page 20: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/20.jpg)
ADD A DEPENDENCY
Find the dependency info in hex.pmEdit mix.exs defp deps do [ {:credo, "~> 0.3", only: [:dev]} ] end
Install it locally$ mix geps.get
Dependencies are an array of tuples.
![Page 21: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/21.jpg)
ADD A DEPENDENCY
Find the dependency info in hex.pmEdit 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
![Page 22: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/22.jpg)
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
![Page 23: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/23.jpg)
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!
![Page 24: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/24.jpg)
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…
![Page 25: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/25.jpg)
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
![Page 26: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/26.jpg)
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
![Page 27: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/27.jpg)
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
![Page 28: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/28.jpg)
CONFIG/CONFIG.EXS
use Mix.Config
config :goth, json: "config/google-creds.json" |> File.read!
![Page 29: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/29.jpg)
CONFIG/CONFIG.EXS
use Mix.Config
config :goth, json: "config/google-creds.json" |> File.read!
Some deps also have their own config
![Page 30: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/30.jpg)
NOW WHAT?
➤ We will write 2 modules:
➤ Ocr.GoogleVision for the API client.
➤ Ocr for our façade
![Page 31: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/31.jpg)
💡 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.
![Page 32: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/32.jpg)
LIB/OCR/GOOGLE_VISION.EX
defmodule Ocr.GoogleVision do def extract_text(image64) do image64 |> make_request |> read_body end # MAGIC!end
![Page 33: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/33.jpg)
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
![Page 34: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/34.jpg)
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
![Page 35: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/35.jpg)
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
![Page 36: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/36.jpg)
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
![Page 37: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/37.jpg)
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)
![Page 38: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/38.jpg)
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
![Page 39: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/39.jpg)
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
![Page 40: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/40.jpg)
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
![Page 41: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/41.jpg)
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
![Page 42: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/42.jpg)
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: nildefp first(:get, data, next) do data |> List.first |> next.()end
![Page 43: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/43.jpg)
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: nildefp first(:get, data, next) do data |> List.first |> next.()end
Only care about body and success http status
![Page 44: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/44.jpg)
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: nildefp first(:get, data, next) do data |> List.first |> next.()end
Only care about body and success http status
Parse JSON response
![Page 45: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/45.jpg)
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: nildefp first(:get, data, next) do data |> List.first |> next.()end
Only care about body and success http status
Parse JSON response
Extract the text
![Page 46: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/46.jpg)
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: nildefp 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
![Page 47: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/47.jpg)
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: nildefp 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
![Page 48: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/48.jpg)
💡 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
![Page 49: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/49.jpg)
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_imageend
![Page 50: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/50.jpg)
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_imageend
![Page 51: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/51.jpg)
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_imageend
![Page 52: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/52.jpg)
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_imageend
![Page 53: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/53.jpg)
FUN!
$ iex -S mixErlang/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_urlGETS ELIKIR PR ACCEPTEDI SAID WHO WANTS TOFUCKING TOUCH ME?Suranyami
:ok
![Page 54: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/54.jpg)
FUN!
$ iex -S mixErlang/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_urlGETS ELIKIR PR ACCEPTEDI SAID WHO WANTS TOFUCKING TOUCH ME?Suranyami
:ok
![Page 55: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/55.jpg)
STATEFUL? STATELESS?Cast your vote!
![Page 56: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/56.jpg)
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)
![Page 57: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/57.jpg)
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} endend
![Page 58: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/58.jpg)
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} endend
Start the process,
![Page 59: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/59.jpg)
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} endend
Start the process, Initial state is an empty map
![Page 60: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/60.jpg)
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} endend
Start the process, Initial state is an empty map
The process has a name
![Page 61: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/61.jpg)
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} endend
Start the process, Initial state is an empty map
The process has a name
Handle 2 types of messages, returning something
![Page 62: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/62.jpg)
FUN WITH TOKEN STORE
$ iex -S mixiex(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}}
![Page 63: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/63.jpg)
FUN WITH TOKEN STORE
$ iex -S mixiex(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
![Page 64: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/64.jpg)
FUN WITH TOKEN STORE
$ iex -S mixiex(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
![Page 65: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/65.jpg)
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}) endend
![Page 66: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/66.jpg)
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}) endend
Provide a client API (usually in the same module) with nicer methods hiding the use of
Genserver.call
![Page 67: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/67.jpg)
LET’S TALK ABOUT OTPProcesses, state, concurrency, supervisors,… oh my!
![Page 68: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/68.jpg)
RECAP
➤ Erlang is:
➤ general-purpose
➤ concurrent
➤ garbage-collected
➤ programming language
➤ and runtime system
➤ Elixir:
➤ Builds on top of all that
![Page 69: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/69.jpg)
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}") endend
iex(2)> BasicMessagePassing.Call.concat "Elixir", "Madrid"Elixir Madrid:okiex(3)> spawn BasicMessagePassing.Call, :concat, ["Elixir", "Madrid"]Elixir Madrid#PID<0.69.0>
![Page 70: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/70.jpg)
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}") endend
iex(2)> BasicMessagePassing.Call.concat "Elixir", "Madrid"Elixir Madrid:okiex(3)> spawn BasicMessagePassing.Call, :concat, ["Elixir", "Madrid"]Elixir Madrid#PID<0.69.0>
Same process
![Page 71: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/71.jpg)
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}") endend
iex(2)> BasicMessagePassing.Call.concat "Elixir", "Madrid"Elixir Madrid:okiex(3)> spawn BasicMessagePassing.Call, :concat, ["Elixir", "Madrid"]Elixir Madrid#PID<0.69.0>
Same process
Spawned process id
![Page 72: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/72.jpg)
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? pidfalse
![Page 73: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/73.jpg)
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))
![Page 74: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/74.jpg)
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
![Page 75: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/75.jpg)
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))
![Page 76: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/76.jpg)
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)
![Page 77: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/77.jpg)
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)>
![Page 78: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/78.jpg)
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
![Page 79: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/79.jpg)
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
![Page 80: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/80.jpg)
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
![Page 81: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/81.jpg)
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
![Page 82: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/82.jpg)
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
![Page 83: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/83.jpg)
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]} endend
![Page 84: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/84.jpg)
A SUPERVISED STACKiex(7)> import Supervisor.Specniliex(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):firstiex(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: :popState: [] (elixir) lib/gen_server.ex:564: GenServer.call/3iex(11)> GenServer.call(:stack_name, :pop):first
![Page 85: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/85.jpg)
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
![Page 86: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/86.jpg)
QUESTIONS?
![Page 87: Hello elixir (and otp)](https://reader030.fdocuments.us/reader030/viewer/2022020214/58d14b811a28ab41128b47c9/html5/thumbnails/87.jpg)
THANKS!Abel Muiño (@amuino) & Rok Biderman (@RokBiderman)