State — Stateful Computations
Many programming tasks—such as parsing, sequential ID generation, and simulations—require updating state across a sequence of steps.
To keep functions pure without mutable globals, state is typically threaded manually via tuples:
This is extremely verbose. Every single function must accept the state as a parameter and return a
tuple of the result and the updated state, even when the state is not the primary concern of that
function. If you insert or remove a step, you must manually rewrite the variable assignments (c1,
c2, etc.).
The common alternative is to introduce a mutable variable:
While this eliminates the parameter noise, it introduces a correctness risk. The state is now shared globally within its scope. Any function can corrupt it, and testing it in isolation requires manual reset hooks.
State<S, A> offers an elegant third path. It models a stateful computation as a pure, immutable
function that takes an initial state S and returns a tuple of a result A and the new state S:
By representing state transitions as a data structure rather than a series of mutable assignments, we compose stateful steps cleanly and execute them once at the boundary of our program.
Creating State Operations
Section titled “Creating State Operations”To construct state transitions, we use the core constructors of State:
State.getreads the current state, returning it as the produced value.State.modifyupdates the state using a mapping function.State.putoverwrites the active state with a new value.State.getsprojects a specific slice from a structured state record.State.resolvelifts a constant value into theStatecontext without touching the state itself.
Transforming and Sequencing
Section titled “Transforming and Sequencing”We can compose our stateful blueprints point-free, allowing the state to flow through our transformations automatically.
Transforming values with map
Section titled “Transforming values with map”map transforms the produced result of a stateful step, leaving the underlying state transition
completely unaffected:
Sequencing transitions with chain
Section titled “Sequencing transitions with chain”chain is the engine of the State container. It threads the output state of one step into the
input state of the next step, allowing you to write a sequence of stateful operations without ever
referencing the state variable explicitly:
Notice the layout. We describe the addition of three items to the cart and read the final total. The intermediate cart state is threaded from step to step behind the scenes.
Extracting Results: The Runners
Section titled “Extracting Results: The Runners”State is lazy — defining a chain does not execute any transitions. To run the computation, we must
pass it an initial state using one of three runner functions:
Full extraction with run
Section titled “Full extraction with run”State.run executes the transitions and returns a tuple containing both the final value and the
final state:
Reading outcomes with evaluate
Section titled “Reading outcomes with evaluate”State.evaluate executes the transitions and returns only the produced value, discarding the
final state:
Reading state changes with execute
Section titled “Reading state changes with execute”State.execute executes the transitions and returns only the final state, discarding the
produced value:
Practical Example: Unique ID Generation
Section titled “Practical Example: Unique ID Generation”A classic use case for State is generating unique, sequential IDs while constructing an immutable
data structure:
Each call to generateId reads the current integer, increments the counter in the state, and
returns the original integer. The state flows through the sequence, ensuring that no two nodes
receive duplicate IDs.
Accumulating values: bind / bindTo
Section titled “Accumulating values: bind / bindTo”When you need to perform multiple sequential stateful operations and gather their results into a
single object, nesting chain and map inside pipelines can become highly complex:
To solve this, you can use bindTo and bind to cleanly accumulate values key-by-key in a flat,
readable pipeline.
bindTo lifts a value into the pipeline’s accumulator object:
bind runs a new stateful operation using the accumulated object and attaches the result to a new
key:
The underlying state transition threads behind the scenes key-by-key perfectly.
Problems it solves
Section titled “Problems it solves”- Stateful parsers and tokenizers: When writing custom data parsers, markdown tokenizers, or
protocol decoders, each operation consumes input, advances cursor offsets, and yields syntax
nodes. Managing this with shared mutable counters creates fragile off-by-one bugs.
Statemodels every step as a pure transition that yields a computed value alongside an updated state object. - Sequential ID generation and seed tracking: In simulations, workflow step engines, or game
loops, operations need to generate incremental IDs, advance random seeds, or track fuel/quota
budgets without relying on global mutable state.
Statethreads accumulated updates sequentially from one step to the next. - Sub-state isolation and modular transitions (
State.focus): In complex state machines, writing transitions that know about the entire root state couples domain helpers to top-level schemas. PairingStatewithLensviaState.focus(lens)allows reusable operations to transform isolated sub-properties (such as updating an inventory or cart sub-model) while running directly against the global state tree. - Multi-step wizard flows and branch rollbacks: In complex onboarding wizards and financial
questionnaires, user responses dictate subsequent questions and update a growing payload. Because
Statetransitions produce fresh state values without mutating the original, implementing branching navigation, back-tracking, and undo/redo snapshots requires zero defensive cloning. - Deterministic state testing: Mutable state can leak across unit tests or cause subtle ordering
issues. Because
Statecomputations are pure functions, tests can execute any sequence of steps with an exact initial state and deterministically verify both the final state and intermediate values.