$ iex
Erlang/OTP 21 [erts-10.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]
Interactive Elixir (1.7.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> c("code_lock.ex")
[CodeLock, CodeLockCommon]
iex(2)> CodeLock.start_link([:a,:b,:c])
Lock
{:ok, #PID<0.109.0>}
iex(3)> CodeLock.down(:a)
:ok
iex(4)> CodeLock.up(:a)
:ok
iex(5)> CodeLock.down(:b)
:ok
iex(6)> CodeLock.up(:b)
:ok
iex(7)> CodeLock.down(:c)
:ok
iex(8)> CodeLock.up(:c)
Unlock
:ok
# wait 10+ seconds
Lock
iex(9)> CodeLock.down(:a)
:ok
iex(10)> CodeLock.up(:a)
:ok
iex(11)> CodeLock.down(:b)
:ok
iex(12)> CodeLock.up(:b)
:ok
# wait 30+ seconds
iex(13)> CodeLock.down(:c)
:ok
iex(14)> CodeLock.up(:c)
:ok
# i.e. doesn't unlock
iex(15)> CodeLock.down(:a)
:ok
iex(16)> CodeLock.up(:a)
:ok
iex(17)> CodeLock.down(:b)
:ok
iex(18)> CodeLock.up(:b)
:ok
iex(19)> CodeLock.down(:c)
:ok
iex(20)> CodeLock.up(:c)
Unlock
:ok
iex(21)> CodeLock.stop()
Lock
:ok
iex(22)>
Created
January 3, 2019 20:57
-
-
Save peerreynders/0eb1e5e98909afe28c79af5955d84ed8 to your computer and use it in GitHub Desktop.
Erlang gen_statem OTP design principles "state_functions" callback mode revised example "translated" to Elixir
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# file: code_lock.ex | |
# Translated from: http://erlang.org/doc/design_principles/statem.html#example-revisited | |
# callback mode: :state_functions | |
# | |
defmodule CodeLockCommon do | |
# Admittedly gratuitous use of a macro | |
# | |
def handle_event(:cast, {:down, button}, data) do | |
{:keep_state, Map.put(data, :button, button)} | |
end | |
def handle_event(:cast, {:up, button}, data) do | |
case data do | |
%{button: ^button} -> | |
{:keep_state, Map.delete(data, :button), [{:next_event, :internal, {:button, button}}]} | |
_ -> | |
:keep_state_and_data | |
end | |
end | |
def handle_event({:call, from}, :code_length, %{length: length}) do | |
{:keep_state_and_data, [{:reply, from, length}]} | |
end | |
defmacro handle(name) do | |
quote do | |
def unquote(name)(event_type, event_content, data), | |
do: unquote(__MODULE__).handle_event(event_type, event_content, data) | |
end | |
end | |
end | |
defmodule CodeLock do | |
@behaviour :gen_statem | |
@name :code_lock_2 | |
require CodeLockCommon | |
def start_link(code), | |
do: :gen_statem.start_link({:local, @name}, __MODULE__, code, []) | |
def down(button), | |
do: :gen_statem.cast(@name, {:down, button}) | |
def up(button), | |
do: :gen_statem.cast(@name, {:up, button}) | |
def code_length(), | |
do: :gen_statem.call(@name, :code_length) | |
## Mandatory callback functions | |
def stop(), | |
do: :gen_statem.stop(@name) | |
def terminate(_reason, state, _data), | |
do: if(state != :locked, do: do_lock(), else: :ok) | |
def code_change(_vsn, state, data, _extra), | |
do: {:ok, state, data} | |
# returns {:ok, state, data} | |
def init(code) do | |
Process.flag(:trap_exit, true) | |
{:ok, :locked, %{code: code, length: length(code)}} | |
end | |
def callback_mode(), | |
do: [:state_functions, :state_enter] | |
defp clear_buttons(data), | |
do: Map.put(data, :buttons, []) | |
def locked(:enter, _state, data) do | |
do_lock() | |
{:keep_state, clear_buttons(data)} | |
end | |
def locked(:state_timeout, :button, data) do | |
{:keep_state, clear_buttons(data)} | |
end | |
def locked(:internal, {:button, button}, %{code: code, length: length, buttons: buttons} = data) do | |
new_buttons = if(length(buttons) < length, do: buttons, else: tl(buttons)) ++ [button] | |
if new_buttons === code do | |
# correct | |
{:next_state, :open, data} | |
else | |
# Incomplete / Incorrect | |
# Time in milliseconds | |
{:keep_state, %{data | buttons: new_buttons}, [{:state_timeout, 30000, :button}]} | |
end | |
end | |
CodeLockCommon.handle(:locked) | |
# def locked(event_type, event_content, data), do: handle_common(event_type, event_content, data) | |
def open(:enter, _state, _data) do | |
do_unlock() | |
{:keep_state_and_data, [{:state_timeout, 10000, :lock}]} | |
end | |
def open(:state_timeout, :lock, data) do | |
{:next_state, :locked, data} | |
end | |
def open(:internal, {:button, _}, _) do | |
{:keep_state_and_data, [:postpone]} | |
end | |
CodeLockCommon.handle(:open) | |
# def open(event_type, event_content, data), do: handle_common(event_type, event_content, data) | |
defp do_lock(), | |
do: IO.puts("Lock") | |
defp do_unlock(), | |
do: IO.puts("Unlock") | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment