Idiomatic Elixir

Idiomatic Elixir

Elixir is growing in popularity day by day. Many developers are approaching it with prior patterns and knowledge, building software that works but that doesn’t leverage many features of Erlang and its BEAM runtime. In this talk we’ll go through some examples and learn how to write great Elixir code.

8f29619526ac4fb3d9b78c82eaa5e40e?s=128

Claudio Ortolina

November 03, 2016
Tweet

Transcript

  1. IDIOMATIC ELIXIR © Erlang Solu,ons Ltd, 2016 1

  2. Hello! © Erlang Solu,ons Ltd, 2016 2

  3. Claudio Ortolina Head of Elixir at Erlang Solu!ons Ltd. @cloud8421

    claudio.ortolina@erlang-solu!ons.com © Erlang Solu,ons Ltd, 2016 3
  4. IDIOMATIC ELIXIR © Erlang Solu,ons Ltd, 2016 4

  5. IDIOMATIC ELIXIR © Erlang Solu,ons Ltd, 2016 5

  6. FLEXIBLE ELIXIR © Erlang Solu,ons Ltd, 2016 6

  7. DEFINITION 1 flexible adjec!ve - /ˈflek.sə.bəl/ • Able to change

    or be changed easily according to the situa!on • Able to bend or to be bent easily without breaking 1 h!p:/ /dic"onary.cambridge.org/dic"onary/english/flexible © Erlang Solu,ons Ltd, 2016 7
  8. WARNING: CODE AHEAD © Erlang Solu,ons Ltd, 2016 8

  9. 1. HANDLING VOLATILE DATA © Erlang Solu,ons Ltd, 2016 9

  10. CACHE STORE defmodule CacheStore do @type key :: term @type

    value :: term @type reason :: term @spec get(key) :: {:ok, value} | {:error, reason} @spec set(key, value) :: :ok @spec start_link() :: {:ok, pid} | {:error, reason} end © Erlang Solu,ons Ltd, 2016 10
  11. v1: AGENT © Erlang Solu,ons Ltd, 2016 11

  12. defmodule MyApp do def start(_type, _args) do children = [

    worker(CacheStore, []) ] ... end end defmodule CacheStore do def start_link() do initial = fn() -> %{} end Agent.start_link(initial, name: __MODULE__) end end © Erlang Solu,ons Ltd, 2016 12
  13. defmodule CacheStore do def set(key, val) do Agent.cast(__MODULE__, fn(store) ->

    Dict.put(store, key, val) end) end def get(key) do Agent.get(__MODULE__, fn(store) -> case Dict.get(store, key) do :error -> {:error, :not_found} value -> {:ok, value} end end) end end © Erlang Solu,ons Ltd, 2016 13
  14. ! SIMPLE © Erlang Solu,ons Ltd, 2016 14

  15. ! STRICTLY CONSISTENT © Erlang Solu,ons Ltd, 2016 15

  16. ! ONE OPERATION AT A TIME © Erlang Solu,ons Ltd,

    2016 16
  17. ! IF AGENT CRASHES, DATA IS LOST © Erlang Solu,ons

    Ltd, 2016 17
  18. v2: GenServer + ETS © Erlang Solu,ons Ltd, 2016 18

  19. GenServer • Generic server behaviour • Used to model client-server

    rela!onship • Server holds data and api to access/mutate it • Used to implement Agent © Erlang Solu,ons Ltd, 2016 19
  20. ETS • Erlang Term Storage • Built-in • Large, in-memory

    data storage • Constant !me access © Erlang Solu,ons Ltd, 2016 20
  21. defmodule CacheStore do use GenServer def start_link do GenServer.start_link(__MODULE__, [],

    name: __MODULE__) end def init([]) do :ets.new(__MODULE__, [:named_table, :bag, :public, read_concurrency: true]) {:ok, []} end end © Erlang Solu,ons Ltd, 2016 21
  22. defmodule CacheStore do ... def set(key, value) do GenServer.cast(__MODULE__, {:set,

    key, value}) end def handle_cast({:set, key, value}, state) do true = :ets.insert(__MODULE__, {key, value}) {:noreply, state} end end © Erlang Solu,ons Ltd, 2016 22
  23. defmodule CacheStore do ... def get(key) do case :ets.lookup(__MODULE__, key)

    do [] -> {:error, :not_found} [{^key, result}] -> {:ok, result} end end def handle_cast({:set, key, value}, state) do true = :ets.insert(__MODULE__, {key, value}) {:noreply, state} end end © Erlang Solu,ons Ltd, 2016 23
  24. ! CONCURRENT WRITES AND READS © Erlang Solu,ons Ltd, 2016

    24
  25. ! EVENTUALLY CONSISTENT © Erlang Solu,ons Ltd, 2016 25

  26. ! IF SERVER CRASHES, DATA IS GONE © Erlang Solu,ons

    Ltd, 2016 26
  27. v3: GenServer + ETS + App © Erlang Solu,ons Ltd,

    2016 27
  28. defmodule CacheStore do def create_cache_table do :ets.new(__MODULE__, [:named_table, :duplicate_bag, :public,

    read_concurrency: true]) end end © Erlang Solu,ons Ltd, 2016 28
  29. defmodule MyApp do def start(_type, _args) do # The app

    process owns the table now CacheStore.create_cache_table children = [ worker(CacheStore, []) ] ... end end © Erlang Solu,ons Ltd, 2016 29
  30. ! ONLY AN APP CRASH CAN AFFECT THE CACHED DATA

    © Erlang Solu,ons Ltd, 2016 30
  31. 2. LONG RUNNING PROCESSES © Erlang Solu,ons Ltd, 2016 31

  32. GAME PROCESS • Game session in a process • Data

    is self-contained • Exposes expensive opera!ons © Erlang Solu,ons Ltd, 2016 32
  33. v1: GenServer © Erlang Solu,ons Ltd, 2016 33

  34. defmodule Game do use GenServer defmodule State do defstruct score:

    0, players: [], ... end def player_avatars(pid) do GenServer.call(pid, :player_avatars) end def handle_call(:player_avatars, _from, state) do ids = Enum.map(state.players, fn(p) -> p.id end) {:ok, avatars} = slow_fetch_urls(ids) {:reply, avatars, state} end end © Erlang Solu,ons Ltd, 2016 34
  35. ! IT WORKS © Erlang Solu,ons Ltd, 2016 35

  36. ! WHILE FETCHING, THE GAME IS BLOCKED © Erlang Solu,ons

    Ltd, 2016 36
  37. ! IF FETCH FAILS, THE GAME DIES © Erlang Solu,ons

    Ltd, 2016 37
  38. 2. GenServer.reply © Erlang Solu,ons Ltd, 2016 38

  39. defmodule Game do ... def player_avatars(pid) do GenServer.call(pid, :player_avatars) end

    def handle_call(:player_avatars, from, state) do spawn(fn() -> ids = Enum.map(state.players, fn(p) -> p.id end) {:ok, avatars} = slow_fetch_urls(ids) GenServer.reply(from, avatars) end) {:noreply, state} end end © Erlang Solu,ons Ltd, 2016 39
  40. ! FETCH HAPPENS IN DIFFERENT PROCESS © Erlang Solu,ons Ltd,

    2016 40
  41. ! WHEN FETCH FAILS, GAME IS UNAFFECTED © Erlang Solu,ons

    Ltd, 2016 41
  42. ! SAME GUARANTEES FOR THE CALLER © Erlang Solu,ons Ltd,

    2016 42
  43. ! EVENTUAL CONSISTENCY © Erlang Solu,ons Ltd, 2016 43

  44. 3. FUNCTIONAL CHAINS © Erlang Solu,ons Ltd, 2016 44

  45. DIGEST USER INPUT • Incoming data • Whitelist, validate, store

    • Each step may fail • Computa!on must return a value © Erlang Solu,ons Ltd, 2016 45
  46. v1: PURE PIPELINE © Erlang Solu,ons Ltd, 2016 46

  47. defmodule CreateUser do @allowed_params ~w(email username) def run(params) do params

    |> whitelist |> validate |> store end def whitelist(params) do Map.take(params, @allowed_params) end end © Erlang Solu,ons Ltd, 2016 47
  48. defmodule MyApp.UserController do def create(params) do case CreateUser.run(params) do {:ok,

    _user} -> resp(conn, 201, :created) _error -> resp(conn, 406, :malformed) end end end © Erlang Solu,ons Ltd, 2016 48
  49. ! WORKS GREAT FOR GOLDEN PATH © Erlang Solu,ons Ltd,

    2016 49
  50. ! FAILS WITH ARGUMENT ERRORS © Erlang Solu,ons Ltd, 2016

    50
  51. v2: TRY..RESCUE © Erlang Solu,ons Ltd, 2016 51

  52. defmodule CreateUser do @allowed_params ~w(email username) def run(params) do try

    do params |> whitelist |> validate |> store rescue error -> error end end def whitelist(params) do Map.take(params, @allowed_params) end end © Erlang Solu,ons Ltd, 2016 52
  53. ! TRY..RESCUE HIDES ERRORS © Erlang Solu,ons Ltd, 2016 53

  54. ! ALL FAILURES ARE TREATED THE SAME © Erlang Solu,ons

    Ltd, 2016 54
  55. v3: WITH MACRO © Erlang Solu,ons Ltd, 2016 55

  56. defmodule CreateUser do @allowed_params ~w(email username) def run(params) do with

    {:ok, whitelisted} <- whitelist(params), {:ok, valid} <- validate(whitelisted) do store(valid) else error -> error end end def whitelist(params) do {:ok, Map.take(params, @allowed_params)} end end © Erlang Solu,ons Ltd, 2016 56
  57. ! CLEARLY EXPRESS WHAT CAN FAIL © Erlang Solu,ons Ltd,

    2016 57
  58. ! CONTROL OVER FAILURE CASES © Erlang Solu,ons Ltd, 2016

    58
  59. ! EXCEPTIONS DIFFERENT FROM ERRORS © Erlang Solu,ons Ltd, 2016

    59
  60. ! NEED TO "TAG" RETURN VALUES © Erlang Solu,ons Ltd,

    2016 60
  61. FLEXIBLE ELIXIR © Erlang Solu,ons Ltd, 2016 61

  62. RICH STANDARD LIBRARY © Erlang Solu,ons Ltd, 2016 62

  63. CHOOSE WHO OWNS DATA © Erlang Solu,ons Ltd, 2016 63

  64. CHOOSE WHO RUNS FUNCTIONS © Erlang Solu,ons Ltd, 2016 64

  65. CHOOSE FAILURE SEMANTICS © Erlang Solu,ons Ltd, 2016 65

  66. THANKS! © Erlang Solu,ons Ltd, 2016 66

  67. Claudio Ortolina Head of Elixir at Erlang Solu!ons Ltd. @cloud8421

    claudio.ortolina@erlang-solu!ons.com © Erlang Solu,ons Ltd, 2016 67