summaryrefslogtreecommitdiff
path: root/u/fn/either.ts
blob: b228af2a1dce96988f7e8603d4d9bc42283f6ba4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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: (err: E | undefined, val: T | undefined) => Tt) => Tt; //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";
};