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 }