Skip to content

Choosing a Container

You pick Maybe for a field that might be missing. Two weeks later the same field has to explain why it is missing, so Maybe becomes Result, and every call site that handled None changes. The container was right for the first week.

This page is the map. Each row names the question you are answering, the type that answers it, and why the neighbours are a worse fit.

The question Use Why not a neighbour
Is the value there? Maybe Absence has no error value to carry.
Did this step fail, and why? Result The first failure ends the operation.
Which of these independent fields are wrong? Validation Collects every failure instead of stopping at the first.
Did it succeed with warnings? These Carries a value and accumulated notes at the same time.
Which phase is this request in? RemoteData Not asked, loading, failed, or succeeded as one state.
What decisions produced this value? Logged Pairs a result with the steps that led to it.

Result, Validation, These, and RemoteData all convert into one another. Convert at the boundary where the requirement changes, not in the middle of a pipeline: Result.to.Validation, Validation.from.Result, RemoteData.from.Result.

When an Op manager already owns the request, derive the rendered state instead of tracking it twice: RemoteData.from.OpState(manager.state) maps idle, in-flight, and settled states onto the four RemoteData variants.

The question Use Why not a neighbour
Lazy work that cannot fail Task No error channel to invent one for.
Lazy work that can fail Task.Result Errors stay typed and an AbortSignal is threaded.
Lazy work that may return nothing Task.Maybe Absence without a fabricated error value.
Several async field checks Task.Validation Runs fields independently and accumulates failures.
Overlapping calls, retries, timeouts Op Decides what happens when callers arrive faster than work settles.
Steps with dependencies Graph Schedules a DAG, runs shared nodes once, cancels siblings.
Sequential writes with rollback Transaction Runs compensating actions in reverse order.
Acquire, use, release Resource Guaranteed cleanup on every exit path.
Many single-key lookups Batcher Coalesces concurrent keys into one batch query.
Reusing a recent successful lookup Cache Answers from storage with expiry and eviction; a memoized task covers one unkeyed thunk.
Protecting a failing dependency CircuitBreaker Fast-fails while the upstream recovers.
Staying inside an API quota RateLimiter Queues or refuses calls so a sliding window is never exceeded.
In-process messaging EventBus Typed delivery with a queue for re-entrant emits.
Keeping a worker running Supervisor Restarts a long-running task under a retry policy and reports its state.
Explicit states and transitions Machine Rejects events the current state does not handle.
Awaiting a result that is already in flight Deferred A latch, not a place to define work.
Pure context or state threading Reader, State No async lifecycle involved.

The execution shape is the same for every definition: create it, optionally interpret it into a running instance, then run it. Reading the Types describes that model once; the async guides apply it.

The question Use Why not a neighbour
Ordered list, safe indexing Arr Bounds-safe helpers; NonEmpty groups keep their guarantee.
Plain object, known keys Rec Structural, enumerable record operations.
Map with arbitrary keys Dict Key identity instead of string keys.
Unique values, set algebra Uniq Union, intersection, and difference on immutable sets.
Text Str Trim, split, truncate, and parse without regex side effects.
Numbers with range rules Num Clamping and division guards.
Exact integer arithmetic BigNum bigint math without floating-point drift.
Boolean tests and branches Bool Named checks and fallbacks inside a pipeline.
Parsing and serialising JSON Json Typed Result instead of thrown SyntaxError.
The question Use Why not a neighbour
Are these two values equal? Equality Structural comparison with custom rules.
Which comes first? Ordering Multi-column ordering with tiebreakers.
Does this value pass a rule? Predicate Composable boolean checks.
Is this value the right shape? Refinement Type guards that narrow, and convert to Maybe or Result.
Merge two configurations? Combinable Defined merge semantics per type.
Update one nested field? Lens Guaranteed path, no spread chain.
Update a field that may be absent? Optional A focus that can miss.
Two values that travel together Pair A tuple with named accessors and no failure semantics.

A real feature uses more than one row. Four worked examples show the seams:

  • A request lifecycle — validate input, run it with a policy, render the resulting state.
  • Per-field validation — one rule per error id, with errors revealed only when a trigger allows it.
  • A dependency graph — schedule reads, write inside a transaction, clean up acquired handles.
  • Events and state — route messages through a bus into a machine and a request manager.

Each recipe is a vertical slice, not a module reference. Read the guide for the module it uses when you need the full surface.