Eparch
EPARCH OF THE CITY (ἔπαρχος τῆς πόλεως), successor of the late Roman URBAN PREFECT, the governor of Constantinople. [^1]
[^1]: The Oxford Dictionary of Byzantium, Vol II.
Eparch is a library that brings type-safe wrappers for some Erlang/OTP behaviours, making your byzantine systems shine with Gleam's type system.
Supported OTP Behaviours
gen_statem: OTP's newest behaviour for finite state machines.gen_event: For creating event managers.
Installation
gleam add eparchQuick Start
The eparch/state_machine module wraps gen_statem with a type-safe API. You must define your states and messages as custom Gleam types, write a single event handler, and wire everything up with a Builder:
import eparch/state_machine as sm
import gleam/erlang/process
type State { Off | On }
type Msg {
Push
GetCount(reply_with: process.Subject(Int))
}
fn handle_event(event, state, data: Int) {
case event, state {
sm.Info(Push), Off -> sm.next_state(On, data + 1, [])
sm.Info(Push), On -> sm.next_state(Off, data, [])
sm.Info(GetCount(reply_with: subj)), _ -> {
process.send(subj, data)
sm.keep_state(data, [])
}
_, _ -> sm.keep_state(data, [])
}
}
pub fn start() {
let assert Ok(machine) =
sm.new(initial_state: Off, initial_data: 0)
|> sm.on_event(handle_event)
|> sm.start
machine.data // Subject(Msg)
}
Synchronous calls via gen_statem:call
For request/reply without embedding a Subject in the message, use the native gen_statem call mechanism, events arrive as sm.Call(from, msg) and replies are sent back with sm.Reply:
import eparch/state_machine as sm
type Msg { Unlock(String) }
fn handle_event(event, state, data) {
case event, state {
sm.Call(from, Unlock(entered)), Locked ->
case entered == data.code {
True -> sm.next_state(Open, data, [sm.Reply(from, Ok(Nil))])
False -> sm.keep_state(data, [sm.Reply(from, Error("Wrong code"))])
}
_, _ -> sm.keep_state(data, [])
}
}State Enter callbacks
You can also opt into state_enter to react whenever the machine enters a new state:
sm.new(initial_state: Locked, initial_data: data)
|> sm.with_state_enter()
|> sm.on_event(handle_event)
|> sm.start
// In handle_event, auto-lock after 5 s when entering "Open"
fn handle_event(event, state, data) {
case event, state {
// ...
sm.Enter(_), Open -> sm.keep_state(data, [sm.StateTimeout(5000)])
// ...
}
}
Key differences from gen_statem
Erlang gen_statem | eparch/state_machine |
|---|---|
Separate handle_call, handle_cast, handle_info |
Single handle_event dispatching on Event |
| Raw action tuples |
Type-safe Action values |
state_enter always on |
Opt-in via with_state_enter() |
| Multiple return tuple formats |
Single Step type |
Full API reference: https://hexdocs.pm/eparch
Development
The project uses devenv and Nix for a hermetic development environment:
nix developOr, if you are already using direnv:
direnv allow .