From 9970036d203ba2d0a46b35ba6fad21d49441cdd4 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 27 Jul 2025 17:03:10 -0700 Subject: hai --- lib/types/fn/optional.ts | 93 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 lib/types/fn/optional.ts (limited to 'lib/types/fn/optional.ts') diff --git a/lib/types/fn/optional.ts b/lib/types/fn/optional.ts new file mode 100644 index 0000000..504e496 --- /dev/null +++ b/lib/types/fn/optional.ts @@ -0,0 +1,93 @@ +import { type Mapper, Predicate, type Supplier, Tagged, isTagged } from '@emprespresso/pengueno'; + +export type MaybeGiven = T | undefined | null; + +export const IOptionalTag = 'IOptional' as const; +export type IOptionalTag = typeof IOptionalTag; +export const isOptional = (o: unknown): o is IOptional => isTagged(o, IOptionalTag); +export class IOptionalEmptyError extends Error {} +export interface IOptional = NonNullable> extends Tagged, Iterable { + readonly move: <_T>(t: MaybeGiven<_T>) => IOptional<_T>; + readonly map: <_T>(mapper: Mapper>) => IOptional<_T>; + readonly filter: (mapper: Predicate) => IOptional; + readonly flatMap: <_T>(mapper: Mapper>>) => IOptional<_T>; + readonly orSome: (supplier: Supplier>) => IOptional; + readonly get: Supplier; + readonly present: Supplier; +} + +type OSomeTag = typeof OSomeTag; +const OSomeTag = 'O.Some' as const; +interface Some extends Tagged { + value: NonNullable; +} + +const ONoneTag = 'O.None' as const; +type ONoneTag = typeof ONoneTag; +interface None extends Tagged {} + +const isNone = (o: unknown): o is None => isTagged(o, ONoneTag); +const isSome = (o: unknown): o is Some => isTagged(o, OSomeTag); + +class _Tagged implements Tagged { + protected constructor(public readonly _tag = IOptionalTag) {} +} + +export class Optional = NonNullable> extends _Tagged implements IOptional { + private constructor(private readonly self: Some | None) { + super(); + } + + public move<_T>(t: MaybeGiven<_T>): IOptional<_T> { + return this.map(() => t); + } + + public orSome(supplier: Supplier>): IOptional { + if (isNone(this.self)) return Optional.from(supplier()); + return this; + } + + public get(): T { + if (isNone(this.self)) throw new IOptionalEmptyError('called get() on None optional'); + return this.self.value; + } + + public filter(mapper: Predicate): IOptional { + if (isNone(this.self) || !mapper(this.self.value)) return Optional.none(); + return Optional.some(this.self.value); + } + + public map<_T>(mapper: Mapper>): IOptional<_T> { + if (isNone(this.self)) return Optional.none(); + return Optional.from(mapper(this.self.value)) as IOptional<_T>; + } + + public flatMap<_T>(mapper: Mapper>>): IOptional<_T> { + if (isNone(this.self)) return Optional.none(); + return Optional.from(mapper(this.self.value)) + .orSome(() => Optional.none()) + .get(); + } + + public present() { + return isSome(this.self); + } + + *[Symbol.iterator]() { + if (isSome(this.self)) yield this.self.value; + } + + static some = NonNullable>(value: T): IOptional { + return new Optional({ value, _tag: OSomeTag }); + } + + private static readonly _none = new Optional({ _tag: ONoneTag }); + static none(): IOptional { + return this._none as unknown as IOptional; + } + + static from = NonNullable>(value: MaybeGiven): IOptional { + if (value === null || value === undefined) return Optional.none(); + return Optional.some(value); + } +} -- cgit v1.2.3-70-g09d2