-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmaybe.ts
175 lines (157 loc) · 4.86 KB
/
maybe.ts
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
const MAYBE = Symbol.for('@@maybe');
type Nullish = null | undefined | void;
type NonNullish = {};
type Mandatory<TValue> = Exclude<TValue, Nullish>;
type MaybeBase<TValue> = {
readonly [MAYBE]: Maybe<TValue>;
/**
* True if the monad does not have a non-nullish value. Otherwise, false.
*
* _NOTE: Always the inverse of the `ok` property._
*/
readonly empty: boolean;
/**
* Get the current error if any.
*/
readonly error: NonNullish | null;
/**
* True if the monad has a non-nullish value. Otherwise, false.
*
* _NOTE: Always the inverse of the `empty` property._
*/
readonly ok: boolean;
/**
* Get the non-nullish value if the monad is `ok`. Otherwise, throws the
* `error` or a new `ReferenceError`.
*/
readonly value: Mandatory<TValue>;
/**
* Get the next monad if the current monad is `empty` and does not have an
* `error`. Otherwise, return the current monad.
*/
else<TNext>(next: TNext | (() => Nullish | TNext) | null | undefined): Maybe<TNext | TValue>;
/**
* Get the next monad if the current monad has an `error`. Otherwise, return
* the current monad.
*/
catch<TNext>(next: (error: NonNullish) => Nullish | TNext): Maybe<TNext | TValue>;
/**
* Get an `empty` monad if the current monad is `ok` and the `predicate`
* returns false. Otherwise, return the current monad.
*/
filter<TNext extends TValue = TValue>(
predicate: ((value: Mandatory<TValue>) => boolean) | ((value: TValue) => value is TNext),
): Maybe<TNext>;
/**
* Get the next monad if the current monad is `ok`. Otherwise, return the
* current `empty` monad.
*/
map<TNext>(next: (value: Mandatory<TValue>) => Maybe<TNext> | Nullish | TNext): Maybe<TNext>;
/**
* Get a one element array containing the non-nullish value if `ok`.
* Otherwise, return an empty array.
*/
toArray(): [] | [Mandatory<TValue>];
};
type MaybeOk<TValue> = MaybeBase<TValue> & {
readonly empty: false;
readonly error: null;
readonly ok: true;
};
type MaybeNotOk<TValue> = MaybeBase<TValue> & {
readonly empty: true;
readonly ok: false;
readonly value: never;
};
/**
* Maybe monad.
*
* Access the current state using the `ok`, `empty`, `value`, and `error` properties,
* or the `toArray` method.
*
* Create derivative monads using the `else*`, `map`, and `filter` methods.
* The `else*` methods are no-ops if `ok` property is true. The `map` and
* `filter` methods are no-ops if the `ok` property is false.
*/
type Maybe<TValue> = MaybeNotOk<TValue> | MaybeOk<TValue>;
const createOk = <TValue>(value: Mandatory<TValue>): MaybeOk<TValue> => {
const instance: MaybeOk<TValue> = {
get [MAYBE]() {
return instance;
},
empty: false,
error: null,
ok: true,
value,
else: () => instance,
catch: () => instance,
filter: (predicate) => maybe<any>(() => (predicate(value) ? instance : maybe.empty())),
map: (next) => maybe(() => next(value)),
toArray: () => [value],
};
return instance;
};
const createEmpty = (error: NonNullish | null): MaybeNotOk<never> => {
const instance: MaybeNotOk<never> = {
get [MAYBE]() {
return instance;
},
empty: true,
error,
ok: false,
get value(): never {
throw error ?? new ReferenceError('maybe instance is empty');
},
else: error == null ? maybe : () => instance,
catch: error == null ? () => instance : (next) => maybe(() => next(error)),
filter: () => instance,
map: () => instance,
toArray: () => [],
};
return instance;
};
/**
* Get a monad for the `init` value.
*
* @param init A value, or value factory.
*/
const maybe = <TValue>(
init: Maybe<TValue> | TValue | (() => Maybe<TValue> | Nullish | TValue) | null | undefined,
): Maybe<TValue> => {
try {
const value = typeof init === 'function' ? (init as () => Maybe<TValue> | Nullish | TValue)() : init;
if (isMaybe(value)) {
return value;
} else if (value == null) {
return maybe.empty();
} else {
return createOk(value as Mandatory<TValue>);
}
} catch (error) {
return maybe.error(error);
}
};
/**
* Get an empty monad with the given `error` value. If the error is not given
* or nullish (ie. null or undefined), a default error will be created.
*/
maybe.error = <TValue = never>(error?: unknown): Maybe<TValue> => {
return createEmpty((error ?? new Error('unknown')) as NonNullish);
};
/**
* Get an empty (not ok) monad, _without_ an error.
*
* **NOTE:** All empty monad instances are the same instance (ie. referentially
* identical).
*/
maybe.empty = <TValue = never>(): Maybe<TValue> => {
return empty;
};
const empty = createEmpty(null);
/**
* Returns true of the `value` is a `Maybe` monad instance. Otherwise, false.
*/
const isMaybe = (value: unknown): value is Maybe<unknown> => {
return Boolean((value as { [MAYBE]?: unknown })?.[MAYBE]);
};
export { isMaybe, type Maybe, maybe };