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); } }