Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Claudio Ortolina

November 03, 2016
Tweet

More Decks by Claudio Ortolina

Other Decks in Programming

Transcript

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

    claudio.ortolina@erlang-solu!ons.com © Erlang Solu,ons Ltd, 2016 3
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. ETS • Erlang Term Storage • Built-in • Large, in-memory

    data storage • Constant !me access © Erlang Solu,ons Ltd, 2016 20
  8. 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
  9. 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
  10. 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
  11. 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
  12. ! ONLY AN APP CRASH CAN AFFECT THE CACHED DATA

    © Erlang Solu,ons Ltd, 2016 30
  13. GAME PROCESS • Game session in a process • Data

    is self-contained • Exposes expensive opera!ons © Erlang Solu,ons Ltd, 2016 32
  14. 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
  15. 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
  16. DIGEST USER INPUT • Incoming data • Whitelist, validate, store

    • Each step may fail • Computa!on must return a value © Erlang Solu,ons Ltd, 2016 45
  17. 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
  18. 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
  19. 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
  20. 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
  21. Claudio Ortolina Head of Elixir at Erlang Solu!ons Ltd. @cloud8421

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