Skip to content

Primitives: cond, sequence, reduce

Primitives — barrel export.

Re-exports small but commonly-missing helpers: cond, condErr, sequence, sequenceAsyncResult, reduce, partitionOption, lift.

Defined in: primitives/partitionOption.ts:26

partitionOption — splits an array of IOption<T> into its Some values and the indices of Nones. The index array is preserved so callers can match the original list (useful when validating a fixed-shape schema).

Unlike separate, which partitions IResultOfT into Ok/Err values, partitionOption returns only Some values plus the indices of Nones (because None carries no payload to preserve). If you need the None indices for IResultOfT, use combineWithAllErrors instead.

import { partitionOption, cond } from '@sandlada/result/primitives';
import { ofSome, ofNone } from '@sandlada/result/option';
import { match } from '@sandlada/result/operators';
const opts = [ofSome(1), ofNone(), ofSome(3), ofNone()];
const parts = partitionOption(opts);
const validated = cond((n: number) => n === 2, 'expected two Some values', parts.some.length);
const count = match((n) => String(n), (e) => e, validated);
// parts.some is [1, 3]; parts.noneIndices is [1, 3]; count === 2 on success
Type Parameter
T
Property Modifier Type
noneIndices readonly readonly number[]
some readonly readonly T[]

cond — the value-aware form of fromPredicate. Returns Ok(value) when the predicate passes, otherwise Err(errorOnFalse).

Unlike fromPredicate, cond always carries the original value through, so it is the natural choice when the failure branch also needs the value (e.g. “value not in allowed set” → Err({ value, allowed })).

If predicate(value) returns true, yields Ok(value); otherwise yields Err(errorOnFalse).

export function cond<T, E>(
predicate: (value: T) => boolean,
errorOnFalse: E,
value: T,
): IResultOfT<T, E>;

Defined in: primitives/cond.ts

import { cond } from '@sandlada/result/primitives';
import { match } from '@sandlada/result/operators';
const value = 5;
const bad = -1;
const passed = cond((n: number) => n > 0, 'must be positive', value);
const failed = cond((n: number) => n > 0, 'must be positive', bad);
const label = match((n) => String(n), (e) => e, passed);
// label === '5'; failed is Err('must be positive') when the third argument fails the predicate

condErr — the inverse of cond. When the predicate passes, returns Err(errorOnTrue); otherwise returns Ok(okValue).

Use it when a presence condition should fail (e.g. “found a forbidden character → Err”).

export function condErr<T, E>(
predicate: (value: T) => boolean,
okValue: T,
errorOnTrue: E,
): IResultOfT<T, E>;

Defined in: primitives/condErr.ts

import { condErr } from '@sandlada/result/primitives';
import { match } from '@sandlada/result/operators';
const invalid = 'alice@x';
const valid = 'no-at';
const rule = 'invalid email';
const rejected = condErr((s: string) => s.includes('@'), invalid, rule);
const accepted = condErr((s: string) => s.includes('@'), valid, rule);
const label = match((s) => s, (e) => e, accepted);
// rejected is Err(rule); label === 'no-at' when the second argument passes the predicate

lift — lift a (possibly throwing) function into the IResultOfT context. This is the fromThrowable cousin that never marks the failure channel: use it for synchronous, total functions where you want the Result shape for pipeline uniformity without an error type.

If fn throws, the error is captured under the supplied errorFn. If no errorFn is given, thrown errors propagate out of lift(...) itself (matching unwrapOr’s documented throw policy).

Note on E = never: the single-argument overload has default error type never. This reflects the type-level guarantee that the function never produces an Err when no errorFn is supplied — but the function itself can still throw at runtime, in which case the throw propagates synchronously to the caller. If you want all errors funneled into the Err channel, supply an errorFn.

export function lift<A extends unknown[], T, E = never>(
fn: (...args: A) => T,
): (...args: A) => IResultOfT<T, E>;
export function lift<A extends unknown[], T, E>(
fn: (...args: A) => T,
errorFn: (error: unknown) => E,
): (...args: A) => IResultOfT<T, E>;

Defined in: primitives/lift.ts

import { lift } from '@sandlada/result/primitives';
import { match } from '@sandlada/result/operators';
const safeParseInt = lift((s: string) => Number.parseInt(s, 10), (e) => new Error(String(e)));
const input = '21';
const parsed = safeParseInt(input);
const total = lift((n: number) => n * 2)(42);
const label = match((n) => String(n), (e) => String(e), parsed);
// label === 21; total is Ok(84); invalid strings map to Err via the errorFn argument

Single pass over opts, accumulating Some values and the indices of Nones.


export function partitionOption<T>(opts: readonly IOption<T>[]): Partitioned<T>;

Defined in: primitives/partitionOption.ts

reduce — fold a list of IResultOfT<T, E> into a single IResultOfT<Acc, E> via a (acc, value, index) => IResultOfT<Acc, E> step. Short-circuits on the first failure returned by either the source list or the step function.

Folds items left-to-right. If any item is Err, the reducer is skipped and the failure is returned. If the reducer itself returns Err, processing stops.

export function reduce<T, E, Acc>(
reducer: (acc: Acc, value: T, index: number) => IResultOfT<Acc, E>,
initial: Acc,
items: readonly IResultOfT<T, E>[],
): IResultOfT<Acc, E>;

Defined in: primitives/reduce.ts

import { reduce } from '@sandlada/result/primitives';
import { ok, err } from '@sandlada/result/factories';
import { match } from '@sandlada/result/operators';
const items = [ok(1), ok(2), ok(3)];
const sum = reduce(
(acc, n) => (n === 0 ? err('zero not allowed') : ok(acc + n)),
0,
items,
);
const broken = reduce((acc, n) => ok(acc + n), 0, [ok(1), err('bad'), ok(3)]);
const total = match((n) => String(n), (e) => e, sum);
// total === 6; broken short-circuits to Err('bad') on the second item in the list

sequence — alias of combine. Provided for readers familiar with Rust/Haskell where “sequence” means turning [Result<T, E>] into Result<T[], E>. The behaviour is identical to combine; pick whichever name matches your codebase’s vocabulary.

Converts [IResultOfT<T, E>] into IResultOfT<readonly T[], E>, short-circuiting on the first failure. The readonly modifier matches combine’s tuple-overload output for runtime-sized readonly arrays.

export function sequence<T, E>(
results: readonly IResultOfT<T, E>[],
): IResultOfT<readonly T[], E>;

Defined in: primitives/sequence.ts

import { sequence } from '@sandlada/result/primitives';
import { ok, err } from '@sandlada/result/factories';
import { match } from '@sandlada/result/operators';
const inputs = [ok(1), ok(2), ok(3)];
const allOk = sequence(inputs);
const short = sequence([ok(1), err('a'), ok(3)]);
const values = match((arr) => arr.join(','), (e) => e, allOk);
// values === '1,2,3'; short stays Err('a') without collecting the third result

Lazy analogue of sequence. Converts AsyncResult<T, E>[] into AsyncResult<T[], E> without executing any inner run(). The returned thunk short-circuits on the first failure when finally awaited.

Equivalent to combine from @sandlada/result/async-result, exposed under a name familiar to ROP practitioners.

export function sequenceAsyncResult<T, E>(
results: readonly AsyncResult<T, E>[],
): AsyncResult<T[], E>;

Defined in: primitives/sequenceAsyncResult.ts

import { sequenceAsyncResult } from '@sandlada/result/primitives';
import { fromResult } from '@sandlada/result/async-result';
import { ok } from '@sandlada/result/factories';
import { match } from '@sandlada/result/operators';
const thunks = [fromResult(ok(1)), fromResult(ok(2))];
const combined = sequenceAsyncResult(thunks);
const r = await combined.run();
const label = match((arr) => String(arr.length), (e) => e, r);
// label === 2; the first Err from any inner run() short-circuits the batch