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
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.
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