State machine concepts
A state machine is a small model of a process. At any instant it is in a configuration: one or more active states, plus a context value. An event arrives, the machine selects a transition, runs the transition’s behavior, and reaches a new configuration.
wireform-state-machine uses the statechart model: ordinary finite-state
machines plus hierarchy, parallel regions, history, entry/exit actions, delayed
transitions, and invoked services.
The core loop
Section titled “The core loop”flowchart LR config["current configuration"] --> event["event"] event --> select["select transitions"] select --> exit["exit states"] exit --> actions["run transition actions"] actions --> enter["enter states"] enter --> stable["stable configuration"] stable --> config
The same loop appears in the Haskell API:
Right boot <- initialize impl ctx0Right next <- step impl (sMachine boot) (EvExternal (mkEvent_ @'SUBMIT))initializeenters the initial configuration.stepprocesses one external event.sMachine nextis the new configuration and context.sTrace nextrecords the microsteps that led there.sEffects nextrecords timers and invocations to start or cancel.
States and configurations
Section titled “States and configurations”A state names a phase of the process: Idle, Loading, Paid,
Failed. A machine’s configuration is the set of active states.
A flat machine has exactly one active state:
stateDiagram-v2 [*] --> Idle Idle --> Loading : FETCH Loading --> Success : done Loading --> Failure : error
A hierarchical chart has ancestors too. If Loading is inside Checkout, the
active configuration contains both the parent and the child:
[Checkout, Loading]Use matches @'Loading machine for a compile-checked state query, or
activeKeys machine when you want the active states as values of your state
enum.
Events
Section titled “Events”An event is the input that asks the machine to move. In this library, an event has a key and a payload type:
type Events = '[ 'FETCH ::: Url , 'CANCEL ::: () ]
mkEvent @'FETCH urlmkEvent_ @'CANCELThat declaration means:
'FETCHalways carries aUrl;'CANCELcarries no payload;- a handler can recover the payload with
onEvent @'FETCH; - sending the wrong payload type does not compile.
Events are not states. With separate key kinds, To 'FETCH is a kind error:
'FETCH belongs to the event kind, not the state kind.
Transitions
Section titled “Transitions”A transition says what happens when a trigger is active:
On 'FETCH ==> To 'LoadingRead it as: when the event 'FETCH arrives and the source state is active,
transition to 'Loading.
A transition can also stay in place and run actions:
On 'PUSH ==> Stay ! '[ 'NotePedestrian ]or enter multiple targets in separate parallel regions:
On 'RESET ==> ToAll '[ 'FormIdle, 'PreviewIdle ]Transition order matters only among transitions that compete in the same state. The first transition whose guard passes wins.
Guards
Section titled “Guards”A guard is a pure predicate that decides whether a transition is enabled:
On 'TIMER ?: 'NoPedestrian ==> To 'GreenOn 'TIMER ==> To 'WalkIf 'NoPedestrian passes, the first transition wins. If it fails, selection
continues and the second transition can fire.
In the implementation registry, a guard is ordinary Haskell code:
mkGuard @'NoPedestrian (\ctx _event -> not (pedestrianWaiting ctx))Guards should not perform effects. Selection may inspect them while deciding which transition to take.
Actions
Section titled “Actions”An action is behavior attached to a transition, entry, or exit. Actions run after exits and before entries for a transition, and they can update context, raise internal events, or request sends to another actor.
Entry '[ 'LogStart ]Exit '[ 'StopSpinner ]On 'FETCH ==> To 'Loading ! '[ 'RememberUrl ]Common implementation constructors:
assign @'RememberUrl updateCtx
effect @'LogStart logStart
raiseEvent @'RetryNow (\_ _ -> mkEvent_ @'RETRY)Use actions for behavior; use guards only for pure decisions.
Context
Section titled “Context”The context is the machine’s typed data value. States describe control flow; context carries data.
For a fetch machine:
data FetchCtx = FetchCtx { retries :: Int , pendingUrl :: Maybe Url }A state like Loading says what phase the process is in. The context says
which URL is loading and how many retries have happened. Keeping those separate
prevents state names from turning into a second data model.
Compound states
Section titled “Compound states”A compound state owns child states and has exactly one active child at a time.
Compound 'Checkout 'Editing '[ State 'Editing '[ On 'SUBMIT ==> To 'Submitting ] , State 'Submitting '[ OnDone ==> To 'Done ] , Final 'Done ] '[]When the machine enters 'Checkout, it also enters 'Editing, the compound’s
initial child. When the active child reaches a final state, the compound emits a
done event; OnDoneOf 'Checkout can react to it from the parent level.
Use compound states to group phases that share transitions, entry/exit behavior, or a lifecycle.
Parallel states
Section titled “Parallel states”A parallel state has several regions active at the same time. Each region is usually a compound state.
Parallel 'Editor '[ Compound 'FormRegion 'FormEditing formStates '[] , Compound 'PreviewRegion 'PreviewReady previewStates '[] ] '[]A single event can affect multiple regions if the transitions do not conflict. The parallel state completes only when every region reaches a final state.
Use parallel states when independent concerns must advance together: form data and preview rendering, upload and validation, transport and authentication.
History
Section titled “History”A history state remembers the last active child of a compound state.
Compound 'Operational 'Green '[ State 'Green '[ On 'TIMER ==> To 'Yellow ] , State 'Yellow '[ On 'TIMER ==> To 'Red ] , State 'Red '[ On 'TIMER ==> To 'Green ] , Hist 'OpHist ] '[ On 'POWER_OUT ==> To 'Flashing ]
State 'Flashing '[ On 'FIXED ==> To 'OpHist ]When power returns, 'OpHist restores the previous child of 'Operational
instead of always starting at 'Green.
Use shallow history when only the immediate child matters. Use deep history when the whole nested configuration should be restored.
Delayed and eventless transitions
Section titled “Delayed and eventless transitions”A delayed transition fires after a state has been active for a duration:
After 30000 ==> To 'TimedOutAn eventless transition fires as soon as its source state is active and any guard passes:
Always ?: 'HasCachedResult ==> To 'SuccessEventless transitions are useful for routing after entry: inspect context, then
move immediately to the right state. They must eventually stop; an unguarded
cycle is reported as EventlessLoop instead of hanging.
Invoked services
Section titled “Invoked services”An invoked service is work owned by a state. It starts when the state is entered and is cancelled when the state exits.
State 'Loading '[ Invoke 'GetUser 'HttpGet '[ OnDone ==> To 'Success ! '[ 'Save ] ] '[ OnError ==> To 'Failure ] , On 'CANCEL ==> To 'Idle ]The invoke id ('GetUser) identifies this running invocation. The service key
('HttpGet) selects the implementation. The OnDone and OnError lists say
how the machine handles success or failure.
Use invokes for work with a lifecycle: network requests, child workflows, subscriptions, callbacks, or actors.
Macrosteps, microsteps, and raised events
Section titled “Macrosteps, microsteps, and raised events”One call to step processes a macrostep: the external event plus all
internal work needed to settle the machine.
Inside a macrostep, the engine performs microsteps:
- choose enabled transitions;
- exit states;
- run transition actions;
- enter target states;
- process raised events and eventless transitions;
- stop when no more internal work is enabled.
raiseEvent adds an event to the current macrostep. sendSelf schedules a new
external event for a later macrostep through the interpreter.
Use prettyTrace or sTrace when transition order is surprising. The trace is
a record of the microsteps.
Effects are requests
Section titled “Effects are requests”The pure step does not sleep, fork, or perform service work. It returns effect requests:
ReqStartTimer ...ReqCancelTimer ...ReqStartInvoke ...ReqCancelInvoke ...The IO interpreter executes those requests. The simulator executes the same requests against a virtual clock. This split is why timer races and service settlement can be tested deterministically.
How this maps to the guide
Section titled “How this maps to the guide”| Concept | API / guide page |
|---|---|
| Name vocabulary | deriveKeyKind, KeyKind, KnownKey; overview |
| Chart structure | Chart, ChartWith, State, Compound, Parallel, Final, Hist; the chart type |
| Events | (:::), mkEvent, mkEvent_, onEvent; events carry typed payloads |
| Guards/actions/services | mkGuard, assign, effect, raiseEvent, mkService; implementing a chart |
| Running | initialize, step, interpret; running a machine |
| Deterministic tests | simulate, SimAdvance, simResolve, prettyTrace; testing |
| Long-lived machines | snapshot, restore, Recovery; persistence |
| Diagrams | mermaid, dot, htmlPage, xstateConfig; visualization |
Next: write the chart type.