summaryrefslogtreecommitdiff
path: root/u/fn/either.ts
blob: bf90f1630620cf9f50b52206ec7611bda1eb61e4 (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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { type Mapper, type Supplier, isObject } from "@emprespresso/pengueno";

type IEitherTag = "IEither";
const iEitherTag: IEitherTag = "IEither";

export interface _Either<LeftT, RightT, T> {
  readonly isLeft: LeftT;
  readonly isRight: RightT;
  readonly value: T;
}
export type Left<E> = _Either<true, false, E>;
export type Right<T> = _Either<false, true, T>;

export interface IEither<E, T> {
  readonly _tag: IEitherTag;

  mapBoth: <_E, _T>(
    errBranch: Mapper<E, _E>,
    okBranch: Mapper<T, _T>,
  ) => IEither<_E, _T>;
  fold: <_T>(folder: Mapper<Left<E> | Right<T>, _T>) => _T;
  moveRight: <_T>(t: _T) => IEither<E, _T>;
  mapRight: <_T>(mapper: Mapper<T, _T>) => IEither<E, _T>;
  mapLeft: <_E>(mapper: Mapper<E, _E>) => IEither<_E, T>;
  flatMap: <_T>(mapper: Mapper<T, IEither<E, _T>>) => IEither<E, _T>;
  flatMapAsync: <_T>(
    mapper: Mapper<T, Promise<IEither<E, _T>>>,
  ) => Promise<IEither<E, _T>>;
}

export class Either<E, T> implements IEither<E, T> {
  private readonly self: Left<E> | Right<T>;

  private constructor(
    err?: E,
    ok?: T,
    public readonly _tag: IEitherTag = iEitherTag,
  ) {
    this.self = <Left<E> | Right<T>>{
      isLeft: typeof err !== "undefined",
      isRight: typeof ok !== "undefined",
      value: typeof err !== "undefined" ? err : ok!,
    };
  }

  public moveRight<_T>(t: _T) {
    return this.mapRight(() => t);
  }

  public fold<_T>(folder: Mapper<Left<E> | Right<T>, _T>): _T {
    return folder(this.self);
  }

  public mapBoth<_E, _T>(
    errBranch: Mapper<E, _E>,
    okBranch: Mapper<T, _T>,
  ): 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<T, IEither<E, _T>>): IEither<E, _T> {
    if (this.self.isRight) return mapper(this.self.value);
    return Either.left<E, _T>(this.self.value);
  }

  public mapRight<_T>(mapper: Mapper<T, _T>): IEither<E, _T> {
    if (this.self.isRight) return Either.right<E, _T>(mapper(this.self.value));
    return Either.left<E, _T>(this.self.value);
  }

  public mapLeft<_E>(mapper: Mapper<E, _E>): 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<T, Promise<IEither<E, _T>>>,
  ): Promise<IEither<E, _T>> {
    if (this.self.isLeft) {
      return Promise.resolve(Either.left<E, _T>(this.self.value));
    }
    return await mapper(this.self.value).catch((err) =>
      Either.left<E, _T>(err),
    );
  }

  static left<E, T>(e: E): IEither<E, T> {
    return new Either<E, T>(e, undefined);
  }
  static right<E, T>(t: T): IEither<E, T> {
    return new Either<E, T>(undefined, t);
  }

  static fromFailable<E, T>(s: Supplier<T>): IEither<E, 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>): Promise<IEither<E, 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";
};