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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
import {
BiMapper,
IOptional,
type Mapper,
Optional,
Predicate,
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 left: Supplier<IOptional<E>>;
readonly right: Supplier<IOptional<T>>;
readonly mapRight: <_T>(mapper: Mapper<T, _T>) => IEither<E, _T>;
readonly filter: (mapper: Predicate<T>) => IEither<E, T>;
readonly mapLeft: <_E>(mapper: Mapper<E, _E>) => IEither<_E, T>;
readonly mapBoth: <_E, _T>(errBranch: Mapper<E, _E>, okBranch: Mapper<T, _T>) => 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>>;
readonly moveRight: <_T>(t: _T) => IEither<E, _T>;
readonly fold: <_T>(leftFolder: Mapper<E, _T>, rightFolder: Mapper<T, _T>) => _T;
readonly joinRight: <O, _T>(other: IEither<E, O>, mapper: (a: O, b: T) => _T) => IEither<E, _T>;
readonly joinRightAsync: <O, _T>(
other: (() => Promise<IEither<E, O>>) | Promise<IEither<E, O>>,
mapper: (a: O, b: T) => _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 filter(mapper: Predicate<T>): IEither<E, T> {
if (isLeft(this.self)) return Either.left<E, T>(this.self.err);
return Either.fromFailable<E, T>(() => this.right().filter(mapper).get());
}
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();
}
public joinRight<O, _T>(other: IEither<E, O>, mapper: BiMapper<O, T, _T>) {
return this.flatMap((t) => other.mapRight((o) => mapper(o, t)));
}
public joinRightAsync<O, _T>(
other: Supplier<Promise<IEither<E, O>>> | Promise<IEither<E, O>>,
mapper: BiMapper<O, T, _T>,
) {
return this.flatMapAsync(async (t) => {
const o = typeof other === 'function' ? other() : other;
return await o.then((other) => other.mapRight((o) => mapper(o, t)));
});
}
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));
}
}
|