summaryrefslogtreecommitdiff
path: root/u/types/fn/either.ts
blob: aa67d4123e2df1c1a6446ba58b555060d54d3dba (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
115
116
117
118
119
120
121
122
123
124
125
import { BiMapper, 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 joinRight<K, E, T>(
        arr: Array<K>,
        mapper: BiMapper<K, T, IEither<E, T>>,
        init: IEither<E, T>,
    ): IEither<E, T> {
        return arr.reduce((acc: IEither<E, T>, x: K) => acc.flatMap((t) => mapper(x, t)), init);
    }

    static joinRightAsync<K, E, T>(
        arr: Array<K>,
        mapper: BiMapper<K, T, Promise<IEither<E, T>>>,
        init: IEither<E, T>,
    ): Promise<IEither<E, T>> {
        return arr.reduce(
            (acc: Promise<IEither<E, T>>, x: K) => acc.then((res) => res.flatMapAsync((t) => mapper(x, t))),
            Promise.resolve(init),
        );
    }

    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));
    }
}