Slide 1

Slide 1 text

@nirev Coupling Data
 and Behaviour 2018-04-17 Guilherme de Maio (in Elixir)

Slide 2

Slide 2 text

@nirev @nirev ?

Slide 3

Slide 3 text

@nirev Based in São Paulo @ Brasil
 
 Elixir @ xerpa.com.br (2015)
 
 SP Elixir Meetups regular ;)
 
 lot’s of Java before

Slide 4

Slide 4 text

@nirev Full-time @ Telnyx (Remote)
 Based in São Paulo @ Brasil
 
 Elixir @ xerpa.com.br (2015)
 
 SP Elixir Meetups regular ;)
 
 lot’s of Java before

Slide 5

Slide 5 text

@nirev Full-time @ Telnyx (Remote)
 Based in São Paulo @ Brasil
 
 Elixir @ xerpa.com.br (2015)
 
 SP Elixir Meetups regular ;)
 
 lot’s of Java before @nirev

Slide 6

Slide 6 text

@nirev

Slide 7

Slide 7 text

@nirev Caller Telnyx Sends Webhooks Issues Commands Telnyx Customer

Slide 8

Slide 8 text

@nirev Data and Behaviour How so?

Slide 9

Slide 9 text

“ @nirev One of the fundamental principles of object-oriented design is to combine data and behavior, so that the basic elements of our system (objects) combine both together. Martin Fowler

Slide 10

Slide 10 text

“ @nirev Finally, another downside to object-oriented programming is the tight coupling between function and data. In fact, the Java programming language forces you to build programs entirely from class hierarchies, restricting all functionality to containing methods in a highly restrictive “Kingdom of Nouns” (Yegge 2006). Fogus/Houser | The Joy of Clojure

Slide 11

Slide 11 text

@nirev Call Control Binary strings via socket

Slide 12

Slide 12 text

@nirev Call Control: needs - Several commands sent via socket as binary strings - Each command can take different parameters - Adding new commands should be easy - Sending commands should be the same for all - Crossconcerns: logging, authorization, etc…

Slide 13

Slide 13 text

@nirev Call Control using Objects Beware: pseudocode

Slide 14

Slide 14 text

@nirev CallControl using Objects interface Command { String render(); }

Slide 15

Slide 15 text

@nirev CallControl using Objects class Commander { def send_command(Command command) { command_str = command.render() Socket.send(command_str) } }

Slide 16

Slide 16 text

@nirev class Play impl Command { string uuid; string audio; string channels; def render() { "playback #{uuid} " +
 " #{audio} #{channels}" } } CallControl using Objects

Slide 17

Slide 17 text

@nirev class Play impl Command { string uuid; string audio; string channels; def render() { "playback #{uuid} " +
 " #{audio} #{channels}" } } class Record impl Command { string uuid; string output; string format; string channels def render() { “record #{uuid} " +
 " #{output} " + "format= #{format}" + “channels= #{channels} } } CallControl using Objects

Slide 18

Slide 18 text

@nirev CallControl using Objects - Adding new commands should be easy class Whatever impl Command { string uuid; string audio; string channels; def render() {…} } ✅

Slide 19

Slide 19 text

@nirev class Commander { def send_command(command) { command_str = command.render() Socket.send(command_str) } } Properties:
 
 - Extensible
 
 - Open/Closed CallControl using Objects

Slide 20

Slide 20 text

@nirev What if we need more methods?? class Whatever impl Command { string uuid; string audio; string channels; def render() {…}
 } CallControl using Objects

Slide 21

Slide 21 text

@nirev What if we need more methods?? class Whatever impl Command { string uuid; string audio; string channels; def render() {…}
 def log() {…} } CallControl using Objects interface Command { String render();
 void log(); }

Slide 22

Slide 22 text

@nirev class Whatever impl Command, Loggable { string uuid; string audio; string channels; def render() {…}
 def log() {…} } CallControl using Objects What if we need more methods?? interface Loggable { void log(); } interface Command { String render(); }

Slide 23

Slide 23 text

@nirev CallControl using Objects - Several commands sent via socket as binary strings - Each command can take different parameters - Adding new commands should be easy - Sending commands should be the same for all - Crossconcerns: logging, authorization, etc… ✅ ✅ ✅ ✅ ✅

Slide 24

Slide 24 text

@nirev Functional Commands ;)

Slide 25

Slide 25 text

@nirev Functional version defmodule Commander do
 def send(command) do command |> render() |> socket_send()
 end
 end

Slide 26

Slide 26 text

@nirev defmodule Play do defstruct [ :uuid, :audio, :channels] end defmodule Record do defstruct [
 :uuid, :output, :format, :channels] end Functional version

Slide 27

Slide 27 text

@nirev defmodule Play do defstruct [ :uuid, :audio, :channels] end defmodule Record do defstruct [
 :uuid, :output, :format, :channels] end defmodule Commander do
 def send(command) do command |> render() |> socket_send()
 end
 
 def render(%Play{} = p) do "playback #{p.uuid} #{p.audio} #{p.channels}” end def render(%Record{} = r) do “record #{r.uuid} " <> “ #{r.output} " <> “format= #{r.format}” <> “channels= #{r.channels} end
 end Functional version

Slide 28

Slide 28 text

@nirev So far:
 
 How to add a new command?
 
 How to add a new function? Functional version

Slide 29

Slide 29 text

@nirev Functional version So far:
 
 How to add a new command?
 
 How to add a new function?

Slide 30

Slide 30 text

@nirev Functional version This is the way of doing this with Erlang. With pattern-matching it's possible to dispatch per struct 
 but it’s not possible to do it following “open/closed” principle. This is the sort of thing that makes the "raison d`être" of Elixir.

Slide 31

Slide 31 text

“ @nirev The problem I have with Erlang is that the language is somehow too simple, making it very hard to eliminate boilerplate and structural duplication. Conversely, the resulting code gets a bit messy, being harder to write, analyze, and modify. After coding in Erlang for some time, I thought that functional programming is inferior to OO, when it comes to efficient code organization. Sasa Juric | Why Elixir

Slide 32

Slide 32 text

@nirev Protocols :)

Slide 33

Slide 33 text

@nirev Protocols defprotocol Command do def render(cmd) end

Slide 34

Slide 34 text

@nirev defimpl Command, for: Play do def render(p) do "playback #{p.uuid} #{p.audio} #{p.channels}" end end defimpl Command, for: Record do def render(r) do "record #{r.uuid} #{r.output} " <> "format= #{r.format} channels= #{r.channels}" end end defmodule Play do defstruct [ :uuid, :audio, :channels] end defmodule Record do defstruct [
 :uuid, :output, :format, :channels] end Protocols

Slide 35

Slide 35 text

@nirev defmodule Commander do def send(command) do command |> Command.render() |> socket_send() end end Protocols

Slide 36

Slide 36 text

@nirev - Several commands sent via socket as binary strings - Each command can take different parameters - Adding new commands should be easy - Sending commands should be the same for all - Crossconcerns: logging, authorization, etc… ✅ ✅ ✅ ✅ ✅ Protocols

Slide 37

Slide 37 text

@nirev - Several commands sent via socket as binary strings - Each command can take different parameters - Adding new commands should be easy - Sending commands should be the same for all - Crossconcerns: logging, authorization, etc… ✅ ✅ ✅ ✅ ✅ Protocols

Slide 38

Slide 38 text

@nirev Protocols: what, why, how

Slide 39

Slide 39 text

@nirev WHAT

Slide 40

Slide 40 text

@nirev Protocols: inspired by Clojure - Provide a high-performance, dynamic polymorphism construct 
 as an alternative to interfaces
 
 - Support best part of interfaces, while avoiding the drawbacks

Slide 41

Slide 41 text

@nirev Protocols: inspired by Clojure (defprotocol P (foo [x]) (bar [x y])) (deftype Foo [a b c] P (foo [x] a) (bar [x y] (+ c y))) (foo (Foo. 1 2 3)) => 1 
 (bar (Foo. 1 2 3) 42) => 45

Slide 42

Slide 42 text

@nirev Protocols: inspired by Clojure (defprotocol P (foo [x]) (bar [x y])) (deftype Foo [a b c] P (foo [x] a) (bar [x y] (+ c y))) (foo (Foo. 1 2 3)) => 1 
 (bar (Foo. 1 2 3) 42) => 45 defprotocol P do def foo(x) def bar(x, y) end defmodule Foo do defstruct [:a, :b, :c] defimpl P do def foo(f), do: f.a def bar(f, y) do: f.c + y end end

Slide 43

Slide 43 text

@nirev Protocols: what can you do defprotocol Size do def size(data) end

Slide 44

Slide 44 text

@nirev Protocols: what can you do defprotocol Size do def size(data) end defimpl Size, for: BitString do def size(string), do: byte_size(string) end defimpl Size, for: Map do def size(map), do: map_size(map) end —> Implement it for native types

Slide 45

Slide 45 text

@nirev Protocols: what can you do defprotocol Size do def size(data) end defimpl Size, for: BitString do def size(string), do: byte_size(string) end defimpl Size, for: Map do def size(map), do: map_size(map) end —> Implement it for native types iex> Size.size "abacate" 7
 iex> Size.size %{a: 1, b: 2} 2

Slide 46

Slide 46 text

@nirev Protocols: what can you do defprotocol Size do def size(data) end defmodule Bag do defstruct [:items] defimpl Size do def size(bag), do: Enum.count(bag.items) end end —> Implement it for structs iex> Size.size %Bag{items: [1,2,3]} 3

Slide 47

Slide 47 text

@nirev Protocols: what can you do defprotocol Size do def size(data) end 
 defmodule Bag do defstruct [:items] end defimpl Size, for Bag do def size(bag), do: Enum.count(bag.items) end —> Implement it for structs, decoupled from module definition

Slide 48

Slide 48 text

@nirev Protocols: what can you do defprotocol Size do def size(data) end defimpl Size, for: Any do def size(_), do: 0 end defmodule Sizeless do @derive [Size] defstruct [:wat] end —> Have defaults iex> Size.size %Sizeless{wat: :wat} 0

Slide 49

Slide 49 text

@nirev Protocols: what can you do defprotocol Size do def size(data) end defimpl Size, for: Any do def size(%{size: size}), do: size def size(_), do: 0 end defmodule Sizeable do @derive [Size] defstruct [:size] end —> Have defaults iex> Size.size %Sizeable{size: 42} 42

Slide 50

Slide 50 text

@nirev Protocols: what can you do

Slide 51

Slide 51 text

@nirev HOW

Slide 52

Slide 52 text

@nirev Protocols: how defmodule Data do defstruct [:x] end %Data{x: 1} == %{ __struct __: Data, x: 1}

Slide 53

Slide 53 text

@nirev Protocols: how defmodule Protocolz do def dispatch(function_name, data) do struct_module = data. __struct __ :erlang.apply(struct_module, function_name, [data]) end end

Slide 54

Slide 54 text

@nirev Protocols: how defmodule Play do defstruct [:id, :audio] def render(p) do "playback #{p.id} #{p.audio}" end end play = %Play{id: "19831", audio: "audio.mp3"} Protocolz.dispatch(:render, play) record = %Record{id: "908301931", output: "record.mp3"} Protocolz.dispatch(:render, record) defmodule Record do defstruct [:id, :output] def render(r) do "record #{r.id} #{r.output}" end end

Slide 55

Slide 55 text

@nirev Protocols: how defmodule Protocolz do defmacro defimplz(protocol, [for: struct_module], [do: block]) do quote do defmodule Module.concat([unquote(protocol), unquote(struct_module)]) do unquote(block) end end end def dispatch(protocol, function_name, data) do struct_module = data. __struct __ impl_module = Module.concat(protocol, struct_module) :erlang.apply(impl_module, function_name, [data]) end end

Slide 56

Slide 56 text

@nirev Protocols: how defimplz Command, for: Play do def render(p) do "playback #{p.id} #{p.audio}" end end play = %Play{id: "19831", audio: "audio.mp3"} Protocolz.dispatch(Command, :render, play) # Command.Play.render() record = %Record{id: "908301931", output: "record.mp3"} Protocolz.dispatch(Command, :render, record) # Command.Record.render() defimplz Command, for: Record do def render(r) do "record #{r.id} #{r.output}" end end

Slide 57

Slide 57 text

@nirev Protocols: how defmacro defprotocol(name, do: block) do quote do defmodule unquote(name) do import Protocol, only: [def: 1] # Invoke the user given block _ = unquote(block) end end end

Slide 58

Slide 58 text

@nirev Protocols: how defmacro def({name, _, args}) do quote do name = unquote(name) arity = unquote(arity) Kernel.def unquote(name)(unquote_splicing(args)) do impl_for!(term).unquote(name)(unquote_splicing(args)) end end end

Slide 59

Slide 59 text

@nirev Protocols: how defprotocol Command do def render(x) end defmodule Command do def render(x) do impl_for!(x).render(x) end defp impl_for!(struct) do target = Module.concat( __MODULE __, struct) case Code.ensure_compiled?(target) do true -> target. __impl __(:target) false -> nil end end end

Slide 60

Slide 60 text

@nirev Protocols: how

Slide 61

Slide 61 text

@nirev Protocols: how defmodule Command do def render(x) do impl_for!(x).render(x) end defp impl_for!(struct) do target = Module.concat( __MODULE __, struct) case Code.ensure_compiled?(target) do true -> target. __impl __(:target) false -> nil end end end

Slide 62

Slide 62 text

@nirev Protocols: how defmodule Command do def render(x) do impl_for!(x).render(x) end defp impl_for!(struct) do target = Module.concat( __MODULE __, struct) case Code.ensure_compiled?(target) do true -> target. __impl __(:target) false -> nil end end end CONSOLIDATION

Slide 63

Slide 63 text

@nirev WHY

Slide 64

Slide 64 text

@nirev String.chars defmodule Product do defstruct title: "", price: 0 defimpl String.Chars do def to_string(%Product{title: title, price: price}) do " #{title}, $ #{price}" end end end

Slide 65

Slide 65 text

@nirev Poison defmodule Person do @derive [Poison.Encoder] defstruct [:name, :age] end defimpl Poison.Encoder, for: Person do def encode(%{name: name, age: age}, opts) do Poison.Encoder.BitString.encode(" #{name} ( #{age})", opts) end end

Slide 66

Slide 66 text

@nirev infinitered/elasticsearch-elixir defimpl Elasticsearch.Document, for: MyApp.Post do def id(post), do: post.id def type(_post), do: "post" def parent(_post), do: false def encode(post) do %{ title: post.title, author: post.author } end end

Slide 67

Slide 67 text

@nirev What about Behaviour?

Slide 68

Slide 68 text

@nirev Behaviour vs Protocols defprotocol Command do @spec render(t) :: String.t def render(cmd) end defmodule Commander do @type r :: :ok | :error @callback render(Command.t) :: r end

Slide 69

Slide 69 text

@nirev Behaviour vs Protocols defmodule FakeCommander do @behaviour Commander def send(_command), do: :ok end defmodule SocketCommander do @behaviour Commander def send(command) do … end end defprotocol Command do @spec render(t) :: String.t def render(cmd) end defmodule Commander do @type r :: :ok | :error @callback send(Command.t) :: r end

Slide 70

Slide 70 text

@nirev Protocols Behaviours Dispatch on type Dispatch on module Contract for Data Contract for modules Elixir-only Erlang too Ex: Encoding/ Serializing structs Ex: sending email via SMTP/IMAP, API or mocks

Slide 71

Slide 71 text

@nirev Bottomline?

Slide 72

Slide 72 text

@nirev Coupling data and behaviour can be GOOD, and Elixir protocols are f!%@#%@ awesome!

Slide 73

Slide 73 text

Guilherme de Maio
 [email protected] Thank You! ❤ ElixirConf.EU 2018 Check us out! 
 www.telnyx.com @nirev