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

DDD: Data Driven Development

DDD: Data Driven Development

Some patterns of functional programming in Elixir. #elixirfest

さっちゃん

June 01, 2019
Tweet

More Decks by さっちゃん

Other Decks in Programming

Transcript

  1. The game server Our game is nearly TCG (trading card

    game; PvP & PvE). Very complex game rule. Should view the complex rule to the users. Have many kind of cards. The calculation should be fast.
  2. Features of Elixir Fault tolerance, soft‑realtime, hot code swap. Parallelism,

    distributed system. Functional programming, immutable data.
  3. spawn_link/1 & send/2 Cons: Message passing is slow. Message queue

    becomes a bottleneck. Cause data copy (e.g. ETS). FP (functional programming) has no cons like this.
  4. def f(a, b, c, d, e, f, g, h, i,

    j, k, l, m, n) do {a, b, c, d, e, f, g, h, i, j, k, l, m, n} end
  5. def f(a, %{b: b, c: %{d: d, e: e}, f:

    f} = s1, g, %{h: h} = s2) do {g, s1, s2} = g(g, s1, s2) s1 = update_in(s1.c.d, &(&1 + 1)) {a + g, d, s1, s2} end
  6. FP primers But they are diffent from Elixir. ML, Haskell

    : Have powerful type‑level cal. Lisp, Clojure : Have powerful macro & implicit state. React/Redux : Isn't immutable.
  7. Complexity shold grow. Function grows to chaos, because a function

    encapsulate it's source code. Data can grow. Data is composable & extensible, because data is explicit.
  8. map %{a: a, b: b} [a: a, b: b] %A{a:

    a, b: b} Maps a key to a value.
  9. tuple or map? Only map is extensible. # NG {a,

    b} = {a, b, c} # OK %{a: a, b: b} = %{a: a, b: b, c: c}
  10. Map is composable, %{a: a, b: b} == Map.merge(%{a: a},

    %{b: b}) but it lacks the data about what it's made from. %{a: a, b: b} == Map.merge(%{}, %{a: a, b: b}) %{a: a, b: b} == Map.merge(%{a: a}, %{b: b}) So map isn't decomposable.
  11. Don't read & modify maps or structs directly. # Sometimes

    NG %A{a: 0}.a %{a: 1 | %A{a: 0}} # OK A.a(%A{a: 0}) get_in(%A{a: 0}[:a]) A.put_a(%A{a: 0}, 1) put_in(%A{a: 0}[:a], 1)
  12. Protocol is meta‑level function that maps data to function. Protocol(T)

    : T -> fun(T, ...) Protocol is extensible, just like a map. Protocols ‑ Elixir https://elixir‑lang.org/getting‑ started/protocols.html Protocols ∙ Elixir School https://elixirschool.com/en/lessons/advanced/protocols/
  13. We can list the current domain of the protocol. (Works

    like STI (single table inheritance) of ActiveRecord/Rails.) defprotocol P do end defmodule A do defstruct [] def type, do: "a" defimpl P do end end data = %{type: "a"} ExampleProtocol |> Protocol.extract_impls([:code.lib_dir(:example, :ebin)]) |> Enum.find(&(&1.type() == data.type)) |> struct(data)
  14. Event sourcing Composed function is not extensible. Put a queue

    between functions. # Not this f2(f1(data)) data |> f1 |> f2 # This data -> queue -> f1 -> queue -> f2 a.k.a. FRP (functional reactive programming) cf. Redux, rx, clojure/core.async
  15. Queue can have an environment. Priority: [p2, p3] -> queue

    -> f(p2) -> p1 -> queue -> f(p1) -> queue -> f(p3)
  16. Linguistic tree The slogan is “Trees arise everywhere.” :) Formal

    linguistics (& linguistic AI) have many patterns to treat complex data. Ex. I'm using: α‑Movement Behaviour tree GTTM (Generative Theory of Tonal Music)
  17. Context have a state. @impl Context def init(args), do: state

    context = new(ContextA, args) Context can perform effects. @impl Context def handle({:effect_a, args}, state, context), do: {:reply, response, state, []} def handle(_, _, _), do: :ignore {response, context} = perform(context, {:effect_a, args}) cf. Algebraic Effects‑poi
  18. Context can be composed. context_ab = new() |> put(ContextA, args)

    |> put(ContextB, args) context_ab = add(put(new(), ContextA, args), put(new(), ContextB, args)) This works like a decomposable map.
  19. Contexts is a noncommutative monoid, just like a keyword list.

    add(ctx_a, ctx_b) == ctx_ab add(add(ctx_a, ctx_b), ctx_c) == add(ctx_a, add(ctx_b, ctx_c)) add(ctx_a, ctx_b) != add(ctx_b, ctx_a) add(ctx_a, new) == add(new, ctx_a) == ctx_a