import { type Mapper, type Supplier, isObject } from "@emprespresso/pengueno"; type IEitherTag = "IEither"; const iEitherTag: IEitherTag = "IEither"; export interface _Either { readonly isLeft: LeftT; readonly isRight: RightT; readonly value: T; } export type Left = _Either; export type Right = _Either; export interface IEither { readonly _tag: IEitherTag; mapBoth: <_E, _T>( errBranch: Mapper, okBranch: Mapper, ) => IEither<_E, _T>; fold: <_T>(folder: Mapper | Right, _T>) => _T; moveRight: <_T>(t: _T) => IEither; mapRight: <_T>(mapper: Mapper) => IEither; mapLeft: <_E>(mapper: Mapper) => IEither<_E, T>; flatMap: <_T>(mapper: Mapper>) => IEither; flatMapAsync: <_T>( mapper: Mapper>>, ) => Promise>; } export class Either implements IEither { private readonly self: Left | Right; private constructor( init: { err?: E; ok?: T }, public readonly _tag: IEitherTag = iEitherTag, ) { this.self = | Right>{ isLeft: "err" in init, isRight: "ok" in init, value: init.err ?? init.ok!, }; } public moveRight<_T>(t: _T) { return this.mapRight(() => t); } public fold<_T>(folder: Mapper | Right, _T>): _T { return folder(this.self); } public mapBoth<_E, _T>( errBranch: Mapper, okBranch: Mapper, ): IEither<_E, _T> { if (this.self.isLeft) return Either.left(errBranch(this.self.value)); return Either.right(okBranch(this.self.value)); } public flatMap<_T>(mapper: Mapper>): IEither { if (this.self.isRight) return mapper(this.self.value); return Either.left(this.self.value); } public mapRight<_T>(mapper: Mapper): IEither { if (this.self.isRight) return Either.right(mapper(this.self.value)); return Either.left(this.self.value); } public mapLeft<_E>(mapper: Mapper): IEither<_E, T> { if (this.self.isLeft) return Either.left<_E, T>(mapper(this.self.value)); return Either.right<_E, T>(this.self.value); } public async flatMapAsync<_T>( mapper: Mapper>>, ): Promise> { if (this.self.isLeft) { return Promise.resolve(Either.left(this.self.value)); } return await mapper(this.self.value).catch((err) => Either.left(err), ); } static left(e: E): IEither { return new Either({ err: e }); } static right(t: T): IEither { return new Either({ ok: t }); } static fromFailable(s: Supplier): IEither { try { return Either.right(s()); } catch (e) { return Either.left(e as E); } } static async fromFailableAsync( s: Supplier>, ): Promise> { return await s() .then((t: T) => Either.right(t)) .catch((e: E) => Either.left(e)); } } export const isEither = (o: unknown): o is IEither => { return isObject(o) && "_tag" in o && o._tag === "IEither"; };