-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoptional.ts
194 lines (179 loc) · 5.32 KB
/
optional.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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import { Err, Ok, Result } from "./result.js";
import { nonNullable, Func } from "./util.types.js";
import { Variant, variant, VariantTypeClass } from "./variant.js";
type OptionalVariants<T extends nonNullable> =
| Variant<"Some", [T]>
| Variant<"None">;
type UnwrapOptional<T> = T extends Optional<infer K>
? K extends Optional
? UnwrapOptional<K>
: K
: T;
class Optional<T extends nonNullable = nonNullable> extends VariantTypeClass<
OptionalVariants<T>
> {
/**
* Maps an `Optional<T>` to an `Optional<M>`.
*
* @param mapper - A function that maps `T` to `M`
* @returns An `Optional<M>`
*/
map<M extends Optional>(mapper: Func<[value: T], M>): M;
map<M extends nonNullable>(mapper: Func<[value: T], M>): Optional<M>;
map(mapper: Func<[any], any>) {
return this.match({
Some(value) {
return toOptional(mapper(value));
},
None() {
return None;
},
});
}
/**
* If `value` is the Some variant it returns the value stored in it,
* otherwise it returns the result of executing the `fallback` function.
*
* @param fallback - A function to call if `value` is the None variant.
* @returns The value stored in the Some variant or the result of calling `fallback`.
*/
fallback(fallback: Func<[], T>): T {
return this.match({
Some(value) {
return value;
},
None() {
return fallback();
},
});
}
/**
* Combines this `Optional<T>` with `Optional<B>` to make `Optional<C>`.
*
* @param b - An `Optional<B>`
* @param combiner - A function that combines `T` and `B` into `C`
* @returns `Optional<C>`
*/
combine<B extends nonNullable, C extends Optional>(
b: Optional<B>,
combiner: Func<[a: T, b: B], C>
): C;
combine<B extends nonNullable, C extends nonNullable>(
b: Optional<B>,
combiner: Func<[a: T, b: B], C>
): Optional<C>;
combine(b: Optional, combiner: Func<[any, any], any>) {
return this.map((a) => b.map((b) => combiner(a, b)));
}
/**
* Converts Some variants to None variants if the filter predicate results in false.
*
* If this variant is None or the `filter` predicate results in false the None variant
* is returned. Otherwise the Some variant is returned.
*
* @param filter - A predicate to determine whether or not to return the Some variant
* or None variant.
* @returns `Optional<T>`
*/
filter(filter: Func<[value: T], boolean>): Optional<T> {
return this.map((value) => (filter(value) ? Some(value) : None));
}
/**
* Converts this `Optional<T>` into `Result<T, E>`
*
* @param error - function to compute `E` if this Optional in the None variant.
* @returns `Result<T, E>`
*/
toResult<E extends nonNullable>(error: () => E): Result<T, E> {
return this.match({
Some(value) {
return Ok<T, E>(value!);
},
None() {
return Err<T, E>(error());
},
});
}
}
/**
* Some variant representing that their is some value.
*
* @param value - A value to store in the Some variant.
* @returns An Some variant
*/
function Some<T extends Optional>(value: T): T;
function Some<T extends nonNullable>(value: T): Optional<T>;
function Some(value: nonNullable) {
if (value == null) {
throw new TypeError(
"Some variant of Optional cannot be constructed with null or undefined"
);
}
if (value instanceof Optional) {
return value;
}
return new Optional(variant("Some", value));
}
/**
* None variant representing that their is no value.
*/
const None = new Optional<never>(variant("None"));
/**
* Converts a nullable type into an Optional variant.
* If the `value` is nullish it returns the None variant
* otherwise it wraps the `value` in the Some variant.
*
* @param value - A nullable value.
* @returns An Optional variant.
*/
const toOptional = <T>(value: T): Optional<NonNullable<T>> =>
value != null ? Some(value) : None;
/**
* Converts a func of type `(A) => B` to a func of type `(Optional<a>) => Optional<B>`
*
* @param mapper - A mapping function `(A) => B`
* @returns `(Optional<A>) => Optional<B>`
*/
function OptionalMapper<A extends nonNullable, B extends Optional>(
mapper: Func<[value: A], B>
): Func<[a: Optional<A>], B>;
function OptionalMapper<A extends nonNullable, B extends nonNullable>(
mapper: Func<[value: A], B>
): Func<[a: Optional<A>], Optional<B>>;
function OptionalMapper(
mapper: Func<[any], any>
): Func<[value: Optional], Optional> {
return (a) => a.map(mapper);
}
/**
* Converts a func of type `(A, B) => C` to a func of type `(Optional<A>, Optional<B>) => Optional<C>`
*
* @param combiner - A combining function `(A, B) => C`
* @returns `(Optional<A>, Optional<B>) => Optional<C>`
*/
function OptionalCombiner<
A extends nonNullable,
B extends nonNullable,
C extends Optional
>(combiner: Func<[a: A, b: B], C>): Func<[a: Optional<A>, b: Optional<B>], C>;
function OptionalCombiner<
A extends nonNullable,
B extends nonNullable,
C extends nonNullable
>(
combiner: Func<[a: A, b: B], C>
): Func<[a: Optional<A>, b: Optional<B>], Optional<C>>;
function OptionalCombiner(
combiner: Func<[any, any], any>
): Func<[a: Optional, b: Optional], Optional> {
return (a, b) => a.combine(b, combiner);
}
export {
type Optional,
type UnwrapOptional,
Some,
None,
toOptional,
OptionalMapper,
OptionalCombiner,
};