Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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.

Slide 6

Slide 6 text

How to develop Elixir servers https://speakerdeck.com/ne̲sachirou/sutetohurudeda‑gui‑mo‑ akusesufalsearusoft‑realtimenagemusabawoeasynitukuru

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

OK. We know about how to develop & operate realtime server with Elixir/Phoenix.

Slide 10

Slide 10 text

But, how to write such a complex game rules in Elixir?

Slide 11

Slide 11 text

Features of Elixir Fault tolerance, soft‑realtime, hot code swap. Parallelism, distributed system. Functional programming, immutable data.

Slide 12

Slide 12 text

The main building blocks of Erlang systems are spawn_link/1 & send/2 .

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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.

Slide 15

Slide 15 text

FP of Elixir Pros: Performant. No message passing. No message queue.

Slide 16

Slide 16 text

FP of Elixir Cons: All state is explicit.

Slide 17

Slide 17 text

All state is explicit. Cons?

Slide 18

Slide 18 text

We MUST write ALL state at function arguments & returns.

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

FP cons Function arguments & returns are not extensible. Composed functions are not extensible.

Slide 22

Slide 22 text

FP primers They resolve the cons. ML, Haskell Lisp, Clojure React/Redux

Slide 23

Slide 23 text

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.

Slide 24

Slide 24 text

How can we survive FP in Elixir?

Slide 25

Slide 25 text

FP patterns

Slide 26

Slide 26 text

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.

Slide 27

Slide 27 text

Express your problem as data.

Slide 28

Slide 28 text

Basic data structures

Slide 29

Slide 29 text

Just a value atom float fun integer pid port reference

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

map %{a: a, b: b} [a: a, b: b] %A{a: a, b: b} Maps a key to a value.

Slide 34

Slide 34 text

tuple or map? Only map is extensible. # NG {a, b} = {a, b, c} # OK %{a: a, b: b} = %{a: a, b: b, c: c}

Slide 35

Slide 35 text

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.

Slide 36

Slide 36 text

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)

Slide 37

Slide 37 text

FP Patterns I'm using. Protocol Event sourcing Linguistic tree Composable context

Slide 38

Slide 38 text

Protocol is a good part of Elixir.

Slide 39

Slide 39 text

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/

Slide 40

Slide 40 text

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)

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Queue can have an environment. Dispatch (relates to protocol): %A{} -> queue -> fa %B{} -> queue -> fb

Slide 43

Slide 43 text

Queue can have an environment. Priority: [p2, p3] -> queue -> f(p2) -> p1 -> queue -> f(p1) -> queue -> f(p3)

Slide 44

Slide 44 text

Queue can have an environment. Modify: a1 -> queue(a1 -> a2) -> f(a2)

Slide 45

Slide 45 text

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)

Slide 46

Slide 46 text

Composable context What "context" means? State. Environment.

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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.

Slide 49

Slide 49 text

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