diff options
Diffstat (limited to 'u/types/fn/either.ts')
-rw-r--r-- | u/types/fn/either.ts | 106 |
1 files changed, 106 insertions, 0 deletions
diff --git a/u/types/fn/either.ts b/u/types/fn/either.ts new file mode 100644 index 0000000..6140ada --- /dev/null +++ b/u/types/fn/either.ts @@ -0,0 +1,106 @@ +import { IOptional, type Mapper, Optional, type Supplier, Tagged, isTagged } from '@emprespresso/pengueno'; + +export const IEitherTag = 'IEither' as const; +export type IEitherTag = typeof IEitherTag; +export const isEither = <E, T>(o: unknown): o is IEither<E, T> => isTagged(o, IEitherTag); +export interface IEither<E, T> extends Tagged<IEitherTag> { + readonly mapBoth: <_E, _T>(errBranch: Mapper<E, _E>, okBranch: Mapper<T, _T>) => IEither<_E, _T>; + readonly fold: <_T>(leftFolder: Mapper<E, _T>, rightFolder: Mapper<T, _T>) => _T; + readonly left: Supplier<IOptional<E>>; + readonly right: Supplier<IOptional<T>>; + readonly moveRight: <_T>(t: _T) => IEither<E, _T>; + readonly mapRight: <_T>(mapper: Mapper<T, _T>) => IEither<E, _T>; + readonly mapLeft: <_E>(mapper: Mapper<E, _E>) => IEither<_E, T>; + readonly flatMap: <_T>(mapper: Mapper<T, IEither<E, _T>>) => IEither<E, _T>; + readonly flatMapAsync: <_T>(mapper: Mapper<T, Promise<IEither<E, _T>>>) => Promise<IEither<E, _T>>; +} + +const ELeftTag = 'E.Left' as const; +type ELeftTag = typeof ELeftTag; +export const isLeft = <E>(o: unknown): o is Left<E> => isTagged(o, ELeftTag); +interface Left<E> extends Tagged<ELeftTag> { + err: E; +} + +const ERightTag = 'E.Right' as const; +type ERightTag = typeof ERightTag; +export const isRight = <T>(o: unknown): o is Right<T> => isTagged(o, ERightTag); +interface Right<T> extends Tagged<ERightTag> { + ok: T; +} + +class _Tagged implements Tagged<IEitherTag> { + protected constructor(public readonly _tag = IEitherTag) {} +} + +export class Either<E, T> extends _Tagged implements IEither<E, T> { + protected constructor(private readonly self: Left<E> | Right<T>) { + super(); + } + + public moveRight<_T>(t: _T) { + return this.mapRight(() => t); + } + + public mapBoth<_E, _T>(errBranch: Mapper<E, _E>, okBranch: Mapper<T, _T>): IEither<_E, _T> { + if (isLeft(this.self)) return Either.left(errBranch(this.self.err)); + return Either.right(okBranch(this.self.ok)); + } + + public mapRight<_T>(mapper: Mapper<T, _T>): IEither<E, _T> { + if (isRight(this.self)) return Either.right(mapper(this.self.ok)); + return Either.left(this.self.err); + } + + public mapLeft<_E>(mapper: Mapper<E, _E>): IEither<_E, T> { + if (isLeft(this.self)) return Either.left(mapper(this.self.err)); + return Either.right(this.self.ok); + } + + public flatMap<_T>(mapper: Mapper<T, IEither<E, _T>>): IEither<E, _T> { + if (isRight(this.self)) return mapper(this.self.ok); + return Either.left<E, _T>(this.self.err); + } + + public async flatMapAsync<_T>(mapper: Mapper<T, Promise<IEither<E, _T>>>): Promise<IEither<E, _T>> { + if (isLeft(this.self)) return Promise.resolve(Either.left(this.self.err)); + return await mapper(this.self.ok).catch((err) => Either.left(err)); + } + + public fold<_T>(leftFolder: Mapper<E, _T>, rightFolder: Mapper<T, _T>): _T { + if (isLeft(this.self)) return leftFolder(this.self.err); + return rightFolder(this.self.ok); + } + + public left(): IOptional<E> { + if (isLeft(this.self)) return Optional.from(this.self.err) as IOptional<E>; + return Optional.none(); + } + + public right(): IOptional<T> { + if (isRight(this.self)) return Optional.from(this.self.ok) as IOptional<T>; + return Optional.none(); + } + + static left<E, T>(e: E): IEither<E, T> { + return new Either({ err: e, _tag: ELeftTag }); + } + + static right<E, T>(t: T): IEither<E, T> { + return new Either({ ok: t, _tag: ERightTag }); + } + + static fromFailable<E, T>(s: Supplier<T>): IEither<E, T> { + try { + return Either.right(s()); + } catch (e) { + return Either.left(e as E); + } + } + + static async fromFailableAsync<E, T>(s: Supplier<Promise<T>> | Promise<T>): Promise<IEither<E, T>> { + return await (typeof s === 'function' ? s() : s) + .then((t: T) => Either.right<E, T>(t)) + .catch((e: E) => Either.left<E, T>(e)); + } +} |