/** * Works with values that may be `undefined`. * * Use this module for plain TypeScript values of type `A | undefined` when * `undefined` is the only absence marker. It is a small alternative to wrapping * values in `Option` when your data already uses `undefined` to mean "no * value". The module includes helpers for mapping defined values, matching both * cases, throwing when a value is missing, adapting throwing functions, and * building reducers or combiners. * * @stability stable * @since 4.0.0 */ import * as Combiner from "./Combiner.ts" import type { LazyArg } from "./Function.ts" import { dual } from "./Function.ts" import * as Reducer from "./Reducer.ts" /** * Maps a defined value with `f`, or returns `undefined` unchanged. * * **When to use** * * Use to apply a pure transformation to an `A | undefined` value while * preserving `undefined` as absence. * * @see {@link match} when you need to handle the `undefined` case explicitly * * @stability stable * @category mapping * @since 4.0.0 */ export const map: { /** * Maps a defined value with `f`, or returns `undefined` unchanged. * * **When to use** * * Use to apply a pure transformation to an `A | undefined` value while * preserving `undefined` as absence. * * @see {@link match} when you need to handle the `undefined` case explicitly * * @stability stable * @category mapping * @since 4.0.0 */ (f: (a: A) => B): (self: A | undefined) => B | undefined /** * Maps a defined value with `f`, or returns `undefined` unchanged. * * **When to use** * * Use to apply a pure transformation to an `A | undefined` value while * preserving `undefined` as absence. * * @see {@link match} when you need to handle the `undefined` case explicitly * * @stability stable * @category mapping * @since 4.0.0 */ (self: A | undefined, f: (a: A) => B): B | undefined } = dual(2, (self, f) => (self === undefined ? undefined : f(self))) /** * Pattern matches on an `A | undefined` value, running `onDefined` when the * value is present or evaluating `onUndefined` when the value is `undefined`. * * **When to use** * * Use when you need to turn an `A | undefined` into a non-optional result by * handling both the defined and undefined branches in one expression. * * @see {@link map} for transforming defined values while preserving `undefined` * @see {@link getOrThrowWith} for throwing when the value is `undefined` instead of returning a fallback branch * * @stability stable * @category pattern matching * @since 4.0.0 */ export const match: { /** * Pattern matches on an `A | undefined` value, running `onDefined` when the * value is present or evaluating `onUndefined` when the value is `undefined`. * * **When to use** * * Use when you need to turn an `A | undefined` into a non-optional result by * handling both the defined and undefined branches in one expression. * * @see {@link map} for transforming defined values while preserving `undefined` * @see {@link getOrThrowWith} for throwing when the value is `undefined` instead of returning a fallback branch * * @stability stable * @category pattern matching * @since 4.0.0 */ ( options: { readonly onUndefined: LazyArg readonly onDefined: (a: A) => C } ): (self: A | undefined) => B | C /** * Pattern matches on an `A | undefined` value, running `onDefined` when the * value is present or evaluating `onUndefined` when the value is `undefined`. * * **When to use** * * Use when you need to turn an `A | undefined` into a non-optional result by * handling both the defined and undefined branches in one expression. * * @see {@link map} for transforming defined values while preserving `undefined` * @see {@link getOrThrowWith} for throwing when the value is `undefined` instead of returning a fallback branch * * @stability stable * @category pattern matching * @since 4.0.0 */ ( self: A | undefined, options: { readonly onUndefined: LazyArg readonly onDefined: (a: A) => C } ): B | C } = dual( 2, (self: A | undefined, { onDefined, onUndefined }: { readonly onUndefined: LazyArg readonly onDefined: (a: A) => C }): B | C => self === undefined ? onUndefined() : onDefined(self) ) /** * Returns the defined value, or throws the value produced by `onUndefined` * when the input is `undefined`. * * **When to use** * * Use when you need fail-fast unwrapping of an `A | undefined` value and want * to provide the thrown error for the undefined case. * * **Details** * * Defined values are returned unchanged. When the input is `undefined`, * `onUndefined` is called and its result is thrown. * * @see {@link getOrThrow} for the default-error sibling * @see {@link match} for handling defined and undefined cases without throwing * * @stability stable * @category getters * @since 4.0.0 */ export const getOrThrowWith: { /** * Returns the defined value, or throws the value produced by `onUndefined` * when the input is `undefined`. * * **When to use** * * Use when you need fail-fast unwrapping of an `A | undefined` value and want * to provide the thrown error for the undefined case. * * **Details** * * Defined values are returned unchanged. When the input is `undefined`, * `onUndefined` is called and its result is thrown. * * @see {@link getOrThrow} for the default-error sibling * @see {@link match} for handling defined and undefined cases without throwing * * @stability stable * @category getters * @since 4.0.0 */ (onUndefined: () => unknown): (self: A | undefined) => A /** * Returns the defined value, or throws the value produced by `onUndefined` * when the input is `undefined`. * * **When to use** * * Use when you need fail-fast unwrapping of an `A | undefined` value and want * to provide the thrown error for the undefined case. * * **Details** * * Defined values are returned unchanged. When the input is `undefined`, * `onUndefined` is called and its result is thrown. * * @see {@link getOrThrow} for the default-error sibling * @see {@link match} for handling defined and undefined cases without throwing * * @stability stable * @category getters * @since 4.0.0 */ (self: A | undefined, onUndefined: () => unknown): A } = dual(2, (self: A | undefined, onUndefined: () => unknown): A => { if (self !== undefined) { return self } throw onUndefined() }) /** * Returns the defined value, or throws a default `Error` when the input is * `undefined`. * * **When to use** * * Use when you need to unwrap a value that should already be defined and a * generic missing-value `Error` is acceptable. * * **Details** * * Defined inputs are returned unchanged. `undefined` throws * `new Error("getOrThrow called on a undefined")`. * * @see {@link getOrThrowWith} for the sibling that lets callers choose the thrown value * @see {@link match} for handling defined and undefined cases without throwing * * @stability stable * @category getters * @since 4.0.0 */ export const getOrThrow: (self: A | undefined) => A = getOrThrowWith(() => new Error("getOrThrow called on a undefined") ) /** * Converts a throwing function into one that returns successful results * unchanged and returns `undefined` when the function throws. * * **When to use** * * Use to adapt exception-throwing functions when `undefined` is the absence * value you want to return for failures. * * **Gotchas** * * Thrown values are discarded. If the wrapped function can successfully return * `undefined`, that success is indistinguishable from a thrown failure. * * @stability stable * @category converting * @since 4.0.0 */ export const liftThrowable = , B>( f: (...a: A) => B ): (...a: A) => B | undefined => (...a) => { try { return f(...a) } catch { return undefined } } /** * Creates a `Reducer` for `UndefinedOr` that prioritizes the first non-`undefined` * value and combines values when both operands are present. * * **When to use** * * Use when you need to reduce values that may be `undefined`, keeping the * first defined value as a fallback and combining only when both operands are * defined. * * **Details** * * Combining `undefined` with `undefined` returns `undefined`. Combining a * defined value with `undefined` keeps the defined value, so the first defined * value wins when only one side is present. When both values are defined, they * are combined with `combiner.combine`. The reducer's initial value is * `undefined`. * * @stability stable * @category constructors * @since 4.0.0 */ export function makeReducer(combiner: Combiner.Combiner): Reducer.Reducer { return Reducer.make((self, that) => { if (self === undefined) return that if (that === undefined) return self return combiner.combine(self, that) }, undefined as A | undefined) } /** * Creates a `Combiner` for `A | undefined` that combines values only when both * operands are defined. * * **When to use** * * Use to lift a `Combiner` so any `undefined` operand makes the combined result * `undefined`. * * **Details** * * - `undefined` combined with any value returns `undefined` * - Any value combined with `undefined` returns `undefined` * - `a` combined with `b` returns `combiner.combine(a, b)` * * @see {@link makeReducerFailFast} if you have a `Reducer` and want to lift it * to `UndefinedOr` values. * * @stability stable * @category constructors * @since 4.0.0 */ export function makeCombinerFailFast(combiner: Combiner.Combiner): Combiner.Combiner { return Combiner.make((self, that) => { if (self === undefined || that === undefined) return undefined return combiner.combine(self, that) }) } /** * Creates a `Reducer` for `A | undefined` by wrapping an existing reducer with * fail-fast semantics. * * **When to use** * * Use to wrap an existing `Reducer` so any `undefined` value aborts the entire * reduction result. * * **Details** * * - Initial value is the wrapped reducer's `initialValue` * - Combining two defined values delegates to the wrapped reducer * - If the accumulator or next value is `undefined`, the reduction returns `undefined` * * @see {@link makeCombinerFailFast} if you only have a `Combiner` and want to * lift it to `UndefinedOr` values. * * @stability stable * @category constructors * @since 4.0.0 */ export function makeReducerFailFast(reducer: Reducer.Reducer): Reducer.Reducer { const combine = makeCombinerFailFast(reducer).combine const initialValue = reducer.initialValue as A | undefined return Reducer.make(combine, initialValue, (collection) => { let out = initialValue for (const value of collection) { out = combine(out, value) if (out === undefined) return out } return out }) }