Optional — Nullable Paths
1.87 kB minified + gzipped
JavaScript provides optional chaining (?.) to safely read through nullable object hierarchies, but
offers no equivalent syntax for immutable writes. Updating an optional nested property immutably
requires nested ternary expressions, null checks, and layered object spreads.
Optional<S, A> models a bidirectional path to a property or index A inside S that may or may
not exist. Reading yields Maybe<A>, while writing or modifying updates the value if the path
exists and acts as an immediate no-op if any segment is absent.
The problem with updating optional paths
Section titled “The problem with updating optional paths”Consider a user settings structure where both the notification preferences and specific channel configurations are optional:
Optional chaining cannot be placed on the left-hand side of an assignment:
To update this immutably using standard JavaScript, every optional layer must be inspected with a ternary guard:
This approach presents three distinct challenges:
- Defensive ternary pyramids: Each optional level doubles the indentation. The core mutation becomes lost inside defensive boilerplate.
- Accidental property injection: If an intermediate level is missing and we provide a fallback
object (
settings.notifications ?? { ... }), we risk unintentionally creating sub-trees that the user never opted into. - Array bounds vulnerabilities: Modifying an element by index in an array
(
users[2]?.permissions) requires cloning the array, verifying thatindex >= 0andindex < length, and rejecting non-integer indices (NaN, floats).
Type Structure & Mental Model
Section titled “Type Structure & Mental Model”An Optional<S, A> pairs a getter that returns Maybe<A> with a setter that returns the original
structure unchanged when the target is absent:
The mental model centers on safe traversal:
flowchart TB
subgraph Read ["Safe Reading (Optional.get)"]
S1["Structure (S)"] --> Get["Optional.get(structure)"]
Get -->|"Path exists"| Present["Maybe.Some(A)"]
Get -->|"Path broken / nullish"| Absent["Maybe.None"]
end
subgraph Write ["Safe Writing (Optional.set)"]
S2["Structure (S)"] & Val["New Value (A)"] --> Set["Optional.set(value)(structure)"]
Set -->|"Target exists"| Updated["Updated Structure (new reference)"]
Set -->|"Target absent"| Unchanged["Original Structure (=== unchanged)"]
end
Absent ~~~ S2
If the path exists, updates produce a new object reference with the leaf modified. If any segment in
the path is missing (null or undefined), Optional.set and Optional.modify short-circuit and
return the input structure reference unchanged.
Creating Optional Paths
Section titled “Creating Optional Paths”Optional.from.property focuses on an optional or nullable property in an object.
Optional.from.index focuses on an element at an array index:
Strict index boundaries
Section titled “Strict index boundaries”Optional.from.index enforces strict numerical validation. It guards against non-integers, negative
numbers, NaN, and Infinity:
Custom accessors
Section titled “Custom accessors”Optional.from.accessors constructs an optional path from custom getter and setter functions. The
getter must return a Maybe:
Reading Values Safely
Section titled “Reading Values Safely”Optional.get extracts the target value wrapped in Maybe<A>. For terminal values,
Optional.getOrElse supplies a default fallback, and Optional.match handles presence and absence
with named cases:
Modifying and Writing Through Optionals
Section titled “Modifying and Writing Through Optionals”Optional.set replaces the target value if the path exists. Optional.modify applies a
transformation function to the focused value:
No-op when path is broken
Section titled “No-op when path is broken”When modifying an object where the target segment is absent, Optional.modify leaves the original
structure intact:
Composing Deep Optional Paths
Section titled “Composing Deep Optional Paths”Optional.andThen chains multiple optional paths together. If any segment along the path is
missing, the entire traversal resolves to None on read, or returns the original root object on
write:
Bridging Lenses and Optionals
Section titled “Bridging Lenses and Optionals”Domain structures frequently mix guaranteed structural containers with optional leaf properties.
Lens and Optional compose directly:
Lens.andThenOptional: Connects an initial guaranteedLensto an innerOptional.Optional.andThenLens: Connects an initialOptionalto an inner guaranteedLens.Lens.to.Optional: Widens anyLensinto anOptional.
Pipeline Composition
Section titled “Pipeline Composition”Here is an end-to-end cloud cluster configuration pipeline. It updates an application deployment manifest, adjusting the replica count of an optional background worker pool, modifying an indexed container volume mount, and formatting an optional telemetry collector endpoint:
Problems it solves
Section titled “Problems it solves”- Modifying properties along optional chains requires nested ternary operators and defensive guards
that obscure business logic.
Optionalperforms reads asMaybeand safely applies writes only when all segments exist. - Manual spread operations on optional fields risk creating default objects where none were
intended.
Optional.modifyandOptional.setact as safe no-ops when paths are broken, preventing unintentional property pollution. - Mutating indexed array values requires defensive bounds checking and array cloning.
Optional.from.indexvalidates indices against boundary and integer rules, rejectingNaN, floating-point values, and negative indices automatically. - Real-world domains combine guaranteed container objects with optional attributes. Composing
LensandOptionalthroughandThenOptionalandandThenLensprovides a unified optic pipeline across heterogeneous data shapes. - Extracting absent properties requires manual fallback management.
Optional.getOrElseandOptional.matchprovide structured ways to unwrap present values or execute fallback logic without scattered null checks.