Eparch

Package Version Hex Docs License

Built with Nix [Nix] Build & Test

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

Installation

gleam add eparch

Quick 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_statemeparch/state_machine
Separate handle_call, handle_cast, handle_infoSingle handle_event dispatching on Event
Raw action tuplesType-safe Action values
state_enter always onOpt-in via with_state_enter()
Multiple return tuple formatsSingle Step type

Full API reference: https://hexdocs.pm/eparch

Development

The project uses devenv and Nix for a hermetic development environment:

nix develop

Or, if you are already using direnv:

direnv allow .
Search Document