Maybe — Explicit Absence
1.51 kB minified + gzipped
A thumbnail worker reads its queue URL from an environment variable. When the variable is absent,
the worker starts with the string undefined as its queue and fails on the first job. The startup
code has no way to say that a URL might be missing.
Core Behavior
Section titled “Core Behavior”Maybe<A> represents an optional value as either Some with a value or None with nothing.
map,chain,filter, andtaprun their callbacks only forSome; aNonereaches every later step unchanged.assignToandassignbuild a record from one present value; the firstNonestops the chain before later assignments run.structinspects fields in key order and returns the firstNone, but JavaScript evaluates the field expressions before the call.recover,getOrElse,match, andto.Resultrun their absence handler only forNone.Nonecarries no reason. A variable that is missing and a value that failed to parse become the same variant.
Reach for Maybe when the only fact the caller needs is whether a value exists.
Result carries the reason a step failed, keeping a missing count
distinguishable from a count that failed to parse. The choosing guide compares
the containers side by side.
Example: Thumbnail Worker Configuration
Section titled “Example: Thumbnail Worker Configuration”A thumbnail worker needs a secure queue URL, a bounded concurrency value, a visibility timeout, and a label for its process manager. The loader reads these settings from environment variables and keeps presence explicit: each setting is either usable or absent.
chain calls the next operation only when a value is present. That operation can return None,
which skips subsequent map and chain callbacks:
flowchart TD
Input["Maybe(worker setting)"] --> Present{"Some?"}
Present -->|Yes| Parse["chain calls parsePositiveInteger"]
Present -->|No| Missing["None passes through"]
Parse -->|Valid| Some["Some(worker count)"]
Parse -->|Rejected| Missing
Some --> Next["Later callbacks run"]
Missing --> Skip["Later callbacks are skipped"]
None does not record whether the setting was missing or failed to parse. Use Result when the
caller needs that distinction.
Loading Worker Configuration
Section titled “Loading Worker Configuration”With raw environment values, each setting needs its own null, blank, and parsing branch:
This check accepts "1e1" as a concurrency of 10, even if the configuration requires decimal
digits. It also leaves the URL format, timeout, and label unchecked. Start by wrapping the queue
variable and trimming it only when it is present:
from.nullable changes only null and undefined into None; map skips trimming when the
variable is missing. An empty string still becomes Some(""). The full loader must reject blank
values, parse the settings, apply range limits, and make a startup decision.
assignTo starts an accumulator after the queue URL has passed validation. Each assign receives
that accumulator, but this worker uses only the environment for its remaining independent settings.
If any required setting becomes None, later assignments and the some handler never run. The
label uses recover, so a missing or blank label becomes Some("thumbnail-worker"). The process
manager receives ThumbnailWorkerBoot and can start the worker only for the ready case.
struct is useful when the settings are already available as independent Maybe values. Its
argument is an ordinary object, so JavaScript evaluates the field expressions before struct is
called; stopping at the first None does not skip that work. Use assign callbacks when later
lookups or parsing should be skipped after an earlier failure.
When Absence Must Be an Error
Section titled “When Absence Must Be an Error”The boot decision above collapses every cause into one message. When a supervisor or health endpoint needs a typed failure, attach an error at the boundary:
to.Result calls its error producer only for None, so the reason can be chosen at the boundary. A
later dependent check can then use Result.chain without re-checking presence.
Core Operations
Section titled “Core Operations”Creating Values
Section titled “Creating Values”| Signature | What it does | Guarantee |
|---|---|---|
Maybe.make.some(value) |
Marks a known setting as present. | Produces Some(value) without changing the value. |
Maybe.make.none() |
Creates an explicit missing-setting result. | Produces None. |
Maybe.from.nullable(value) |
Admits an environment value at the boundary. | null and undefined become None; every other value becomes Some. |
Maybe.from.Predicate(predicate)(value) |
Accepts a raw setting only when it passes a test. | A failed predicate produces None; a type guard also narrows the value type. |
Maybe.tryCatch(operation) |
Runs synchronous parsing code such as new URL(raw). |
A synchronous throw produces None. |
Maybe.from.Result(result) / from.Validation(validation) |
Discards an existing error at a boundary where only presence matters. | Success becomes Some; failure becomes None. |
Combining Values
Section titled “Combining Values”| Signature | What it does | Guarantee |
|---|---|---|
Maybe.assignTo(key) |
Starts a configuration accumulator from a present value. | None remains None. |
Maybe.assign(key, operation) |
Adds a dependent or independent setting to an accumulator. | Stops before the next assignment when the current value or operation result is None. |
Maybe.struct(fields) |
Combines independent optional settings into one record. | Visits own keys in order and stops at the first None. |
Maybe.apply(argument) |
Feeds a present argument into a present curried function. | Returns None if the function or its argument is absent. |
Maybe.sequence.Result(maybeResult) |
Moves a result outside an optional setting. | Some(Err) remains an error; None becomes Ok(None). |
Transforming Values
Section titled “Transforming Values”| Signature | What it does | Guarantee |
|---|---|---|
Maybe.map(transform) |
Converts a present setting, such as trimming a label. | The transform does not run for None. |
Maybe.chain(transform) |
Runs a step that can itself reject a setting, such as integer parsing. | Avoids nested Maybe values and bypasses the step for None. |
Maybe.filter(predicate) |
Rejects a present value that violates a rule, such as a concurrency limit. | A failed predicate becomes None; a type guard narrows Some. |
Maybe.recover(fallback) |
Supplies another optional value after an absence. | The fallback runs only for None. |
Maybe.tap(effect) / Maybe.tapNone(effect) |
Records a startup metric without changing the pipeline value. | Each runs its effect only for its matching case and returns the original Maybe. |
Running Code and Leaving Maybe
Section titled “Running Code and Leaving Maybe”| Signature | What it does | Return type |
|---|---|---|
Maybe.match({ some, none }) |
Handles both startup outcomes at the process boundary. | The shared return type of the two handlers. |
Maybe.getOrElse(fallback) |
Extracts a setting or supplies a plain fallback. | A or the fallback type; the fallback is lazy. |
Maybe.is.some(maybe) / Maybe.is.none(maybe) |
Narrows a value for an imperative integration point. | A TypeScript type guard. |
Maybe.to.nullable(maybe) / to.undefined(maybe) |
Returns to an API that expects a nullable primitive. | A or null; A or undefined. |
Maybe.to.Result(onNone) / to.Validation(onNone) |
Attaches an error value when absence crosses a stricter boundary. | Result(E, A) or Validation(E, A). |
Failure Behavior and Edge Cases
Section titled “Failure Behavior and Edge Cases”A None says only that the configuration value is unavailable or unacceptable. The example treats a
missing variable, blank text, malformed URL, non-decimal integer, and excessive limit alike. That is
appropriate when the process manager needs one safe reject message. If an operator needs to know
which rule failed, validate with Result from the first parsing step so each
failure retains its reason; converting an already-empty Maybe to Result can provide only a new
generic error.
For example, a missing worker count and a rejected count become the same None, so converting later
cannot recover their different causes:
Maybe.tryCatch catches only synchronous exceptions thrown while it calls operation. It does not
wrap the callbacks passed to map, chain, filter, tap, recover, getOrElse, or match; an
exception from one of them escapes to the caller. Protect code that can throw before putting it in a
Maybe pipeline. It also does not await promises: Maybe.tryCatch(() => fetchConfiguration())
creates Some(Promise) and a later rejection remains a rejected promise.