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

4b071f90c5d9c0a58e2d9076460b7be4?s=128

さっちゃん

June 01, 2019
Tweet

Transcript

  1. DDD: Data Driven Development Some patterns of functional programming in

    Elixir.
  2. .。oO(さっちゃんですよヾ(〃l ̲ l)ノ゙ ☆)

  3. None
  4. I'm creating a realtime fighting game server in Elixir/Phoenix.

  5. 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.
  6. How to develop Elixir servers https://speakerdeck.com/ne̲sachirou/sutetohurudeda‑gui‑mo‑ akusesufalsearusoft‑realtimenagemusabawoeasynitukuru

  7. How to scale PubSub by Redis https://speakerdeck.com/ne̲sachirou/phoenix‑at‑scale

  8. How to deploy Elixir servers https://speakerdeck.com/ne̲sachirou/elixir‑on‑containers

  9. OK. We know about how to develop & operate realtime

    server with Elixir/Phoenix.
  10. But, how to write such a complex game rules in

    Elixir?
  11. Features of Elixir Fault tolerance, soft‑realtime, hot code swap. Parallelism,

    distributed system. Functional programming, immutable data.
  12. The main building blocks of Erlang systems are spawn_link/1 &

    send/2 .
  13. spawn_link/1 & send/2 Pros: Easy to make concurrency. Have shared

    & implicit state.
  14. 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.
  15. FP of Elixir Pros: Performant. No message passing. No message

    queue.
  16. FP of Elixir Cons: All state is explicit.

  17. All state is explicit. Cons?

  18. We MUST write ALL state at function arguments & returns.

  19. 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
  20. 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
  21. FP cons Function arguments & returns are not extensible. Composed

    functions are not extensible.
  22. FP primers They resolve the cons. ML, Haskell Lisp, Clojure

    React/Redux
  23. 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.
  24. How can we survive FP in Elixir?

  25. FP patterns

  26. 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.
  27. Express your problem as data.

  28. Basic data structures

  29. Just a value atom float fun integer pid port reference

  30. binary <<_::_*n>> Expression for other system.

  31. tuple {a, b} The position have a meaning.

  32. list [x | xs] [a: a, b: b] 'ab' Recrusive.

  33. map %{a: a, b: b} [a: a, b: b] %A{a:

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

    b} = {a, b, c} # OK %{a: a, b: b} = %{a: a, b: b, c: c}
  35. 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.
  36. 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)
  37. FP Patterns I'm using. Protocol Event sourcing Linguistic tree Composable

    context
  38. Protocol is a good part of Elixir.

  39. 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/
  40. 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)
  41. 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
  42. Queue can have an environment. Dispatch (relates to protocol): %A{}

    -> queue -> fa %B{} -> queue -> fb
  43. Queue can have an environment. Priority: [p2, p3] -> queue

    -> f(p2) -> p1 -> queue -> f(p1) -> queue -> f(p3)
  44. Queue can have an environment. Modify: a1 -> queue(a1 ->

    a2) -> f(a2)
  45. 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)
  46. Composable context What "context" means? State. Environment.

  47. 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
  48. 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.
  49. 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