$ 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]
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
# 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 21:02
-
-
Save peerreynders/009acfacf67e4b6289d982f4e800ac23 to your computer and use it in GitHub Desktop.
Erlang gen_statem OTP design principles "handle_event_function" 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: :handle_event_function | |
# | |
defmodule CodeLock do | |
@behaviour :gen_statem | |
@name :code_lock_2 | |
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: [:handle_event_function, :state_enter] | |
defp clear_buttons(data), | |
do: Map.put(data, :buttons, []) | |
## State: :locked | |
## | |
def handle_event(:enter, _state, :locked, data) do | |
do_lock() | |
{:keep_state, clear_buttons(data)} | |
end | |
def handle_event(:state_timeout, :button, :locked, data) do | |
{:keep_state, clear_buttons(data)} | |
end | |
def handle_event( | |
:internal, | |
{:button, button}, | |
:locked, | |
%{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 | |
## State: :open | |
## | |
def handle_event(:enter, _state, :open, _data) do | |
do_unlock() | |
{:keep_state_and_data, [{:state_timeout, 10000, :lock}]} | |
end | |
def handle_event(:state_timeout, :lock, :open, data) do | |
{:next_state, :locked, data} | |
end | |
def handle_event(:internal, {:button, _}, :open, _) do | |
{:keep_state_and_data, [:postpone]} | |
end | |
## Common events | |
def handle_event(:cast, {:down, button}, _state, data) do | |
{:keep_state, Map.put(data, :button, button)} | |
end | |
def handle_event(:cast, {:up, button}, _state, 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, _state, %{length: length}) do | |
{:keep_state_and_data, [{:reply, from, length}]} | |
end | |
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