Skip to content

Instantly share code, notes, and snippets.

@schmalz
Created September 18, 2017 07:33
Show Gist options
  • Save schmalz/85d198727191b677944c3b0d63f8b3b8 to your computer and use it in GitHub Desktop.
Save schmalz/85d198727191b677944c3b0d63f8b3b8 to your computer and use it in GitHub Desktop.
Designing for Scalability with Erlang/OTP - Ch 6 - Coffee FSM - Elixir/OTP
defmodule Coffee do
use GenStateMachine, callback_mode: :state_functions
# Client API
def start_link() do
GenStateMachine.start_link(__MODULE__, {}, [name: __MODULE__])
end
def stop() do
GenStateMachine.stop(__MODULE__)
end
def tea() do
GenStateMachine.cast(__MODULE__, {:selection, :tea, 100})
end
def espresso() do
GenStateMachine.cast(__MODULE__, {:selection, :espresso, 100})
end
def americano() do
GenStateMachine.cast(__MODULE__, {:selection, :americano, 150})
end
def cappuccino() do
GenStateMachine.cast(__MODULE__, {:selection, :cappuccino, 150})
end
def pay(amount) do
GenStateMachine.cast(__MODULE__, {:pay, amount})
end
def cancel() do
GenStateMachine.cast(__MODULE__, :cancel)
end
def cup_removed() do
GenStateMachine.cast(__MODULE__, :cup_removed)
end
# Server Callbacks
def init(_) do
HW.reboot()
HW.display("make your selection")
{:ok, :selection, {}}
end
def selection(:cast, {:selection, brew, price}, _data) do
HW.display("please pay ~w", price)
{:next_state, :payment, {brew, price, 0}}
end
def selection(:cast, {:pay, amount}, data) do
HW.return_change(amount)
{:next_state, :selection, data}
end
def selection(:cast, _other, data) do
{:next_state, :selection, data}
end
def payment(:cast, {:pay, amount}, {brew, price, paid})
when amount + paid < price do
new_paid = amount + paid
HW.display("please pay ~w", price - new_paid)
{:next_state, :payment, {brew, price, new_paid}}
end
def payment(:cast, {:pay, amount}, {brew, price, paid})
when amount + paid >= price do
new_paid = amount + paid
HW.display("preparing ~w", brew)
if new_paid > price do
HW.return_change(new_paid - price)
end
HW.drop_cup()
HW.prepare(brew)
HW.display("remove ~w", brew)
{:next_state, :remove, {}}
end
def payment(:cast, :cancel, {_brew, _price, paid}) do
if paid > 0 do
HW.return_change(paid)
end
HW.display("make your selection")
{:next_state, :selection, {}}
end
def payment(:cast, _other, data) do
{:next_state, :payment, data}
end
def remove(:cast, :cup_removed, _data) do
HW.display("make your selection")
{:next_state, :selection, {}}
end
def remove(:cast, {:pay, amount}, _data) do
HW.return_change(amount)
{:next_state, :remove, {}}
end
def remove(:cast, _other, _data) do
{:next_state, :remove, {}}
end
def terminate(_reason, _state, {_brew, _price, paid}) when paid > 0 do
HW.return_change(paid)
:ok
end
def terminate(_reason, _state, {}) do
:ok
end
end
defmodule HW do
def display(format) do
IO.puts("display: " <> format)
end
def display(format, arg) do
:io.format("display: " <> format <> "~n", [arg])
end
def return_change(amount) do
IO.puts("machine: return #{amount} in change")
end
def drop_cup() do
IO.puts("machine: drop cup")
end
def prepare(brew) do
IO.puts("machine: prepare #{brew}")
end
def reboot() do
IO.puts("machine: reboot hardware")
end
end
@schmalz
Copy link
Author

schmalz commented Sep 18, 2017

This implementation uses the gen_statem wrapper GenStateMachine. This rtequired the following to be added to the mix.exs file.

defp deps do
  [
    {:gen_state_machine, "~> 2.0"}
  ]
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment