Skip to content

Instantly share code, notes, and snippets.

@mprymek
Created April 18, 2015 16:21
Show Gist options
  • Save mprymek/1ebb4c28247879f70463 to your computer and use it in GitHub Desktop.
Save mprymek/1ebb4c28247879f70463 to your computer and use it in GitHub Desktop.
Example of a specialization pattern in Elixir - using a protocol this time
#
# Another example of specialization in Elixir. This time using
# a protocol.
#
# I think it's a cleaner implementation.
#
defprotocol MyProto do
def base_data(state)
def fun1(state)
def fun2(state)
end
# general base module
defmodule ImplBase.State do
defstruct base_data: nil
end
defmodule ImplBase do
alias ImplBase.State
@doc "Initialize ImplBase data"
def init do
%State{base_data: :some_base_data}
end
defmacro __using__([]) do
quote do
def base_data(state), do: state.base_data
def fun1(state) do
IO.puts "fun1 - ImplBase implementation. data=#{inspect MyProto.base_data(state)}"
state
end
def fun2(state) do
IO.puts "fun2 - ImplBase implementation. data=#{inspect MyProto.base_data(state)}"
state
end
defoverridable [base_data: 1, fun1: 1, fun2: 1]
end
end
end
# specific module no 1
# - implementation without using any processes
defmodule Impl1.State do
defstruct base_data: nil, impl1_data: nil
end
defmodule Impl1 do
use ImplBase
alias Impl1.State
@doc "Initialize Impl1 data"
def init do
%State{base_data: ImplBase.init, impl1_data: :some_impl1_data}
end
end
defimpl MyProto, for: Impl1.State do
alias Impl1.State
use ImplBase
def fun1(s=%State{}) do
IO.puts "fun1 - Impl1. data=#{inspect s}"
s
end
end
# specific module no 2
# - implementation using Agent to store the data
#
# NOTE: we still have the "two structures" problem here - we must use one data structure
# as a state wich allows specific MyProto implementation and another
# datastructure to be fed into Agent.
# datastructured passed to the user
defmodule Impl2.State1 do
defstruct agent: nil
end
# internal datastructure kept in Agent
defmodule Impl2.State2 do
defstruct base_data: nil, impl2_data: nil
end
defmodule Impl2 do
alias Impl2.State1
alias Impl2.State2
use ImplBase
@doc "Initialize Impl2 data"
def init do
{:ok,a} = Agent.start_link fn -> %State2{base_data: ImplBase.init, impl2_data: :some_impl2_data} end
%State1{agent: a}
end
end
defimpl MyProto, for: Impl2.State1 do
alias Impl2.State1
alias Impl2.State2
use ImplBase
def base_data(%State1{agent: a}), do: Agent.get(a, fn %State2{base_data: bd} -> bd end)
def fun1(s=%State1{agent: a}) do
data = Agent.get a, fn x -> x end
IO.puts "fun1 - Impl2. data=#{inspect data}"
s
end
end
#
# main
#
# NOTE: we have a big advantage here: we call MyProto.fun1 and not Impl1.fun1 as in the previous example
Impl1.init
|> MyProto.fun1
|> MyProto.fun2
Impl2.init
|> MyProto.fun1
|> MyProto.fun2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment