summaryrefslogtreecommitdiff
path: root/u/fn/either.ts
diff options
context:
space:
mode:
Diffstat (limited to 'u/fn/either.ts')
-rw-r--r--u/fn/either.ts97
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";
+};