Skip to content

Instantly share code, notes, and snippets.

@peerreynders
Created January 3, 2019 20:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save peerreynders/0eb1e5e98909afe28c79af5955d84ed8 to your computer and use it in GitHub Desktop.
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
# 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
$ 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)> 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment