export type Option = T | null | undefined; export function isSome(opt: Option): opt is T { return opt != null; } export function isNone(opt: Option): opt is null | undefined { return opt == null; } export function map(opt: Option, fn: (t: T) => U): Option { return isSome(opt) ? fn(opt) : null; } export function flatMap( opt: Option, fn: (t: T) => Option, ): Option { return isSome(opt) ? fn(opt) : null; } export function filter( opt: Option, predicate: (t: T) => boolean, ): Option { return isSome(opt) && predicate(opt) ? opt : null; } export function getOrElse(opt: Option, defaultValue: T): T { return isSome(opt) ? opt : defaultValue; } export function getOrElseLazy(opt: Option, fn: () => T): T { return isSome(opt) ? opt : fn(); } export function getOrThrow(opt: Option, error?: string | Error): T { if (isSome(opt)) return opt; if (error instanceof Error) throw error; throw new Error(error ?? "Expected value but got null/undefined"); } export function tap(opt: Option, fn: (t: T) => void): Option { if (isSome(opt)) fn(opt); return opt; } export function match( opt: Option, handlers: { some: (t: T) => U; none: () => U }, ): U { return isSome(opt) ? handlers.some(opt) : handlers.none(); } export function toArray(opt: Option): T[] { return isSome(opt) ? [opt] : []; } export function fromArray(arr: T[]): Option { return arr.length > 0 ? arr[0] : null; } export function zip(a: Option, b: Option): Option<[T, U]> { return isSome(a) && isSome(b) ? [a, b] : null; } export function zipWith( a: Option, b: Option, fn: (t: T, u: U) => R, ): Option { return isSome(a) && isSome(b) ? fn(a, b) : null; } export function or(a: Option, b: Option): Option { return isSome(a) ? a : b; } export function orLazy(a: Option, fn: () => Option): Option { return isSome(a) ? a : fn(); } export function and(a: Option, b: Option): Option { return isSome(a) ? b : null; }