diff options
Diffstat (limited to 'u/fn/either.ts')
-rw-r--r-- | u/fn/either.ts | 97 |
1 files changed, 97 insertions, 0 deletions
diff --git a/u/fn/either.ts b/u/fn/either.ts new file mode 100644 index 0000000..8b233bf --- /dev/null +++ b/u/fn/either.ts @@ -0,0 +1,97 @@ +import type { BiMapper, Mapper, Supplier } from "@emprespresso/pengueno"; +import { isObject } from "../leftpadesque/mod.ts"; + +type IEitherTag = "IEither"; +const iEitherTag: IEitherTag = "IEither"; + +export interface IEither<E, T> { + readonly _tag: IEitherTag; + mapBoth: <Ee, Tt>( + errBranch: Mapper<E, Ee>, + okBranch: Mapper<T, Tt>, + ) => IEither<Ee, Tt>; + fold: <Tt>(folder: BiMapper<E | undefined, T | undefined, Tt>) => Tt; + moveRight: <Tt>(t: Tt) => IEither<E, Tt>; + mapRight: <Tt>(mapper: Mapper<T, Tt>) => IEither<E, Tt>; + mapLeft: <Ee>(mapper: Mapper<E, Ee>) => IEither<Ee, T>; + flatMap: <Tt>(mapper: Mapper<T, IEither<E, Tt>>) => IEither<E, Tt>; + flatMapAsync: <Tt>( + mapper: Mapper<T, Promise<IEither<E, Tt>>>, + ) => Promise<IEither<E, Tt>>; +} + +export class Either<E, T> implements IEither<E, T> { + private constructor( + private readonly err?: E, + private readonly ok?: T, + public readonly _tag: IEitherTag = iEitherTag, + ) {} + + public moveRight<Tt>(t: Tt) { + return this.mapRight(() => t); + } + + public fold<R>(folder: BiMapper<E | undefined, T | undefined, R>): R { + return folder(this.err ?? undefined, this.ok ?? undefined); + } + + public mapBoth<Ee, Tt>( + errBranch: Mapper<E, Ee>, + okBranch: Mapper<T, Tt>, + ): Either<Ee, Tt> { + if (this.err !== undefined) return Either.left(errBranch(this.err)); + return Either.right(okBranch(this.ok!)); + } + + public flatMap<Tt>(mapper: Mapper<T, Either<E, Tt>>): Either<E, Tt> { + if (this.ok !== undefined) return mapper(this.ok); + return Either.left<E, Tt>(this.err!); + } + + public mapRight<Tt>(mapper: Mapper<T, Tt>): IEither<E, Tt> { + if (this.ok !== undefined) return Either.right<E, Tt>(mapper(this.ok)); + return Either.left<E, Tt>(this.err!); + } + + public mapLeft<Ee>(mapper: Mapper<E, Ee>): IEither<Ee, T> { + if (this.err !== undefined) return Either.left<Ee, T>(mapper(this.err)); + return Either.right<Ee, T>(this.ok!); + } + + public async flatMapAsync<Tt>( + mapper: Mapper<T, Promise<IEither<E, Tt>>>, + ): Promise<IEither<E, Tt>> { + if (this.err !== undefined) { + return Promise.resolve(Either.left<E, Tt>(this.err)); + } + return await mapper(this.ok!).catch((err) => Either.left<E, Tt>(err as E)); + } + + static left<E, T>(e: E) { + return new Either<E, T>(e); + } + + static right<E, T>(t: T) { + return new Either<E, T>(undefined, t); + } + + static fromFailable<E, T>(s: Supplier<T>) { + try { + return Either.right<E, T>(s()); + } catch (e) { + return Either.left<E, T>(e as E); + } + } + + static async fromFailableAsync<E, T>(s: Promise<T>) { + try { + return Either.right<E, T>(await s); + } catch (e) { + return Either.left<E, T>(e as E); + } + } +} + +export const isEither = <E, T>(o: unknown): o is IEither<E, T> => { + return isObject(o) && "_tag" in o && o._tag === "IEither"; +}; |