onlyfuncs 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +138 -2
- package/package.json +29 -4
- package/src/function/index.js +109 -0
- package/src/index.js +10 -0
- package/src/monoid/index.js +121 -0
- package/src/option/index.js +321 -0
- package/src/predicate/index.js +72 -0
- package/src/readonly-array/index.js +136 -0
- package/src/result/index.js +455 -0
- package/src/semigroup/index.js +112 -0
- package/src/task/index.js +246 -0
- package/src/task-result/index.js +754 -0
- package/src/validation/index.js +122 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import * as Result from "../result/index.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A present value. The payload may itself be null or undefined.
|
|
5
|
+
*
|
|
6
|
+
* @template A
|
|
7
|
+
* @typedef {Object} Some
|
|
8
|
+
* @property {"some"} type - Identifies a present value.
|
|
9
|
+
* @property {A} value - The wrapped value.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* An absent value.
|
|
14
|
+
*
|
|
15
|
+
* @typedef {Object} None
|
|
16
|
+
* @property {"none"} type - Identifies absence.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A value that is either present or absent.
|
|
21
|
+
*
|
|
22
|
+
* @template A
|
|
23
|
+
* @typedef {Some<A> | None} Option
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Wraps a value in Some, preserving object references.
|
|
28
|
+
* Unlike fromNullable, null and undefined are treated as present values.
|
|
29
|
+
*
|
|
30
|
+
* @template A
|
|
31
|
+
* @param {A} value - The value to wrap.
|
|
32
|
+
* @returns {Some<A>} A new Some.
|
|
33
|
+
* @example
|
|
34
|
+
* some("Link"); // { type: "some", value: "Link" }
|
|
35
|
+
*/
|
|
36
|
+
export const some = (value) => ({ type: "some", value });
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The shared value representing absence. Treat this object as immutable.
|
|
41
|
+
*
|
|
42
|
+
* @type {None}
|
|
43
|
+
* @example
|
|
44
|
+
* none; // { type: "none" }
|
|
45
|
+
*/
|
|
46
|
+
export const none = { type: "none" };
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Checks whether an Option is Some, regardless of its payload's truthiness.
|
|
51
|
+
*
|
|
52
|
+
* @template A
|
|
53
|
+
* @param {Option<A>} option - The Option to inspect, not an arbitrary value.
|
|
54
|
+
* @returns {option is Some<A>} Whether the option has the Some tag.
|
|
55
|
+
* @example
|
|
56
|
+
* isSome(some(undefined)); // true
|
|
57
|
+
*/
|
|
58
|
+
export const isSome = (option) => option.type === "some";
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Checks whether an Option is None.
|
|
63
|
+
*
|
|
64
|
+
* @template A
|
|
65
|
+
* @param {Option<A>} option - The Option to inspect, not an arbitrary value.
|
|
66
|
+
* @returns {option is None} Whether the option has the None tag.
|
|
67
|
+
* @example
|
|
68
|
+
* isNone(none); // true
|
|
69
|
+
*/
|
|
70
|
+
export const isNone = (option) => option.type === "none";
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Converts null and undefined to None; wraps every other value in Some.
|
|
75
|
+
* Values such as 0, false, and an empty string remain present.
|
|
76
|
+
*
|
|
77
|
+
* @template A
|
|
78
|
+
* @param {A} value - The possibly missing value.
|
|
79
|
+
* @returns {Option<NonNullable<A>>} Some for a present value, otherwise None.
|
|
80
|
+
* @example
|
|
81
|
+
* fromNullable(0); // { type: "some", value: 0 }
|
|
82
|
+
* fromNullable(null); // { type: "none" }
|
|
83
|
+
*/
|
|
84
|
+
export const fromNullable = (value) =>
|
|
85
|
+
value === null || value === undefined ? none : some(value);
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Returns Some when the predicate passes, otherwise None.
|
|
90
|
+
* Calls the predicate with the input, including undefined. Exceptions propagate.
|
|
91
|
+
*
|
|
92
|
+
* @template A
|
|
93
|
+
* @param {(value: A) => boolean} predicate - Called once with the supplied value.
|
|
94
|
+
* @returns {(value: A) => Option<A>} A function testing and wrapping a value.
|
|
95
|
+
* @example
|
|
96
|
+
* fromPredicate(health => health > 0)(3); // { type: "some", value: 3 }
|
|
97
|
+
* fromPredicate(value => value === undefined)(undefined); // { type: "some", value: undefined }
|
|
98
|
+
*/
|
|
99
|
+
export const fromPredicate = (predicate) => (value) =>
|
|
100
|
+
predicate(value) ? some(value) : none;
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Converts Ok to Some and Err to None, discarding the error.
|
|
105
|
+
* Successful null and undefined values remain present.
|
|
106
|
+
*
|
|
107
|
+
* @template A, E
|
|
108
|
+
* @param {import("../result/index.js").Result<A, E>} result - The Result to convert.
|
|
109
|
+
* @returns {Option<A>} The converted Option.
|
|
110
|
+
* @example
|
|
111
|
+
* fromResult(Result.ok("Link")); // { type: "some", value: "Link" }
|
|
112
|
+
*/
|
|
113
|
+
export const fromResult = (result) =>
|
|
114
|
+
Result.isOk(result) ? some(result.value) : none;
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Transforms a Some value; skips None. Wraps the output without flattening
|
|
119
|
+
* or awaiting it. Callback exceptions propagate.
|
|
120
|
+
*
|
|
121
|
+
* @template A, B
|
|
122
|
+
* @param {(value: A) => B} transform - Receives the entire Some payload.
|
|
123
|
+
* @returns {(option: Option<A>) => Option<B>} A function mapping Some values.
|
|
124
|
+
* @example
|
|
125
|
+
* map(name => name.toUpperCase())(some("Link")); // { type: "some", value: "LINK" }
|
|
126
|
+
* map(name => some(name.toUpperCase()))(some("Link"));
|
|
127
|
+
* // { type: "some", value: { type: "some", value: "LINK" } }
|
|
128
|
+
*/
|
|
129
|
+
export const map = (transform) => (option) =>
|
|
130
|
+
isSome(option) ? some(transform(option.value)) : none;
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Chains a Some value into another Option; skips None.
|
|
135
|
+
* Returns the callback's Option directly. Callback exceptions propagate.
|
|
136
|
+
*
|
|
137
|
+
* @template A, B
|
|
138
|
+
* @param {(value: A) => Option<B>} transform - Receives the payload and returns an Option.
|
|
139
|
+
* @returns {(option: Option<A>) => Option<B>} A function chaining Options.
|
|
140
|
+
* @example
|
|
141
|
+
* const petName = flatMap(hero => fromNullable(hero.pet));
|
|
142
|
+
* petName(some({ pet: "Epona" })); // { type: "some", value: "Epona" }
|
|
143
|
+
* petName(some({})); // { type: "none" }
|
|
144
|
+
*/
|
|
145
|
+
export const flatMap = (transform) => (option) =>
|
|
146
|
+
isSome(option) ? transform(option.value) : none;
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Removes one nested Option layer. An outer None stays absent.
|
|
151
|
+
*
|
|
152
|
+
* @template A
|
|
153
|
+
* @param {Option<Option<A>>} option - An Option whose Some payload is another Option.
|
|
154
|
+
* @returns {Option<A>} The inner Option, or the shared None for an outer None.
|
|
155
|
+
* @example
|
|
156
|
+
* flatten(some(some("Link"))); // { type: "some", value: "Link" }
|
|
157
|
+
*/
|
|
158
|
+
export const flatten = (option) =>
|
|
159
|
+
isSome(option) ? option.value : none;
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Keeps the original Some when its payload passes the predicate.
|
|
164
|
+
* Returns None on failure and skips the predicate for None. Exceptions propagate.
|
|
165
|
+
*
|
|
166
|
+
* @template A
|
|
167
|
+
* @param {(value: A) => boolean} predicate - Called once with a Some payload.
|
|
168
|
+
* @returns {(option: Option<A>) => Option<A>} A function filtering Some values.
|
|
169
|
+
* @example
|
|
170
|
+
* filter(health => health > 0)(some(0)); // { type: "none" }
|
|
171
|
+
*/
|
|
172
|
+
export const filter = (predicate) => (option) =>
|
|
173
|
+
isSome(option) && predicate(option.value) ? option : none;
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Calls a fallback only for None; preserves an existing Some unchanged.
|
|
178
|
+
* Callback exceptions propagate.
|
|
179
|
+
*
|
|
180
|
+
* @template B
|
|
181
|
+
* @param {() => Option<B>} fallback - Called without arguments to supply an Option.
|
|
182
|
+
* @returns {<A>(option: Option<A>) => Option<A | B>} A function supplying a fallback Option.
|
|
183
|
+
* @example
|
|
184
|
+
* orElse(() => some("Link"))(none); // { type: "some", value: "Link" }
|
|
185
|
+
*/
|
|
186
|
+
export const orElse = (fallback) => (option) =>
|
|
187
|
+
isSome(option) ? option : fallback();
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Runs an effect for Some and returns the original Option.
|
|
192
|
+
* Skips the effect for None. The effect's result is ignored, promises are not
|
|
193
|
+
* awaited, and synchronous exceptions propagate.
|
|
194
|
+
*
|
|
195
|
+
* @template A
|
|
196
|
+
* @param {(value: A) => unknown} effect - Observes the Some value.
|
|
197
|
+
* @returns {(option: Option<A>) => Option<A>} A function observing an Option.
|
|
198
|
+
* @example
|
|
199
|
+
* const seen = [];
|
|
200
|
+
* tap(name => { seen.push(name); })(some("Link"));
|
|
201
|
+
* seen; // ["Link"]
|
|
202
|
+
*/
|
|
203
|
+
export const tap = (effect) => (option) => {
|
|
204
|
+
if (isSome(option)) {
|
|
205
|
+
effect(option.value);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return option;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Unwraps Some or calls fallback for None. Callback exceptions propagate.
|
|
214
|
+
*
|
|
215
|
+
* @template B
|
|
216
|
+
* @param {() => B} fallback - Called without arguments to supply the fallback value.
|
|
217
|
+
* @returns {<A>(option: Option<A>) => A | B} A function unwrapping an Option.
|
|
218
|
+
* @example
|
|
219
|
+
* getOrElse(() => "Link")(none); // "Link"
|
|
220
|
+
*/
|
|
221
|
+
export const getOrElse = (fallback) => (option) =>
|
|
222
|
+
isSome(option) ? option.value : fallback();
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Calls the matching handler and returns its output directly.
|
|
227
|
+
* Callback exceptions propagate.
|
|
228
|
+
*
|
|
229
|
+
* @template A, B, C
|
|
230
|
+
* @param {Object} handlers - Callbacks for the two possible branches.
|
|
231
|
+
* @param {(value: A) => B} handlers.onSome - Called with the Some payload.
|
|
232
|
+
* @param {() => C} handlers.onNone - Called without arguments for None.
|
|
233
|
+
* @returns {(option: Option<A>) => B | C} A function handling either branch.
|
|
234
|
+
* @example
|
|
235
|
+
* const greet = match({ onSome: name => `Hello, ${name}`, onNone: () => "Nobody here" });
|
|
236
|
+
* greet(some("Link")); // "Hello, Link"
|
|
237
|
+
*/
|
|
238
|
+
export const match = ({ onSome, onNone }) => (option) =>
|
|
239
|
+
isSome(option) ? onSome(option.value) : onNone();
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Converts Some to a one-element array and None to an empty array.
|
|
244
|
+
* Creates a new array while preserving the payload reference.
|
|
245
|
+
*
|
|
246
|
+
* @template A
|
|
247
|
+
* @param {Option<A>} option - The Option to convert.
|
|
248
|
+
* @returns {A[]} A new array containing zero or one value.
|
|
249
|
+
* @example
|
|
250
|
+
* toArray(some("Link")); // ["Link"]
|
|
251
|
+
*/
|
|
252
|
+
export const toArray = (option) =>
|
|
253
|
+
isSome(option) ? [option.value] : [];
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Combines two Some payloads or returns None if either input is absent.
|
|
258
|
+
* The combiner runs only when both inputs are Some. Exceptions propagate.
|
|
259
|
+
*
|
|
260
|
+
* @template A, B, C
|
|
261
|
+
* @param {(left: A, right: B) => C} combine - Combines the two Some values.
|
|
262
|
+
* @returns {(left: Option<A>, right: Option<B>) => Option<C>} A function combining two Options.
|
|
263
|
+
* @example
|
|
264
|
+
* zipWith((a, b) => a + b)(some(2), some(3)); // { type: "some", value: 5 }
|
|
265
|
+
*/
|
|
266
|
+
export const zipWith = (combine) => (left, right) =>
|
|
267
|
+
isSome(left) && isSome(right) ? some(combine(left.value, right.value)) : none;
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Collects Some payloads in iteration order, stopping at the first None.
|
|
272
|
+
* Empty input returns Some containing an empty array.
|
|
273
|
+
*
|
|
274
|
+
* @template A
|
|
275
|
+
* @param {Iterable<Option<A>>} options - A finite iterable of Options to collect.
|
|
276
|
+
* @returns {Option<A[]>} Some containing a new array, or the shared None.
|
|
277
|
+
* @example
|
|
278
|
+
* all([some("Link"), some("Zelda")]); // { type: "some", value: ["Link", "Zelda"] }
|
|
279
|
+
* all([some("Link"), none]); // { type: "none" }
|
|
280
|
+
*/
|
|
281
|
+
export const all = (options) => {
|
|
282
|
+
const outputs = []
|
|
283
|
+
|
|
284
|
+
for (const option of options) {
|
|
285
|
+
if (isNone(option)) {
|
|
286
|
+
return none;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
outputs.push(option.value);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return some(outputs);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Transforms values in iteration order and collects their Some payloads.
|
|
298
|
+
* Stops at the first None without transforming later inputs. Exceptions propagate.
|
|
299
|
+
* Empty input returns Some containing an empty array.
|
|
300
|
+
*
|
|
301
|
+
* @template A, B
|
|
302
|
+
* @param {(value: A) => Option<B>} transform - Called once per visited value, with no index.
|
|
303
|
+
* @returns {(values: Iterable<A>) => Option<B[]>} A function collecting transformed values.
|
|
304
|
+
* @example
|
|
305
|
+
* traverse(name => fromNullable(name))(["Link", null]); // { type: "none" }
|
|
306
|
+
*/
|
|
307
|
+
export const traverse = (transform) => (values) => {
|
|
308
|
+
const outputs = []
|
|
309
|
+
|
|
310
|
+
for (const value of values) {
|
|
311
|
+
const option = transform(value);
|
|
312
|
+
|
|
313
|
+
if (isNone(option)) {
|
|
314
|
+
return none;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
outputs.push(option.value);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return some(outputs);
|
|
321
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {none, some} from "../option/index.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A synchronous check of a single value. Predicate helpers do not await promises.
|
|
5
|
+
*
|
|
6
|
+
* @template A
|
|
7
|
+
* @callback Predicate
|
|
8
|
+
* @param {A} value - The value to check.
|
|
9
|
+
* @returns {boolean} Whether the value passes the check.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Negates a predicate's result. Calls it with the input, including undefined.
|
|
14
|
+
* Callback exceptions propagate.
|
|
15
|
+
*
|
|
16
|
+
* @template A
|
|
17
|
+
* @param {Predicate<A>} predicate - Called once with the original input value.
|
|
18
|
+
* @returns {Predicate<A>} The negated predicate.
|
|
19
|
+
* @example
|
|
20
|
+
* const defeated = not(health => health > 0);
|
|
21
|
+
* defeated(0); // true
|
|
22
|
+
*/
|
|
23
|
+
export const not = (predicate) => (value) => !predicate(value);
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Requires all predicates to pass, checking left to right on each invocation.
|
|
28
|
+
* Stops at the first falsy result. With no predicates, always returns true.
|
|
29
|
+
* Callback exceptions propagate and prevent later checks from running.
|
|
30
|
+
*
|
|
31
|
+
* @template A
|
|
32
|
+
* @param {...Predicate<A>} predicates - Synchronous checks receiving the same input value.
|
|
33
|
+
* @returns {Predicate<A>} The combined predicate.
|
|
34
|
+
* @example
|
|
35
|
+
* const canAttack = and(hero => hero.health > 0, hero => hero.stamina > 0);
|
|
36
|
+
* canAttack({ health: 3, stamina: 0 }); // false
|
|
37
|
+
*/
|
|
38
|
+
export const and = (...predicates) => (value) =>
|
|
39
|
+
predicates.every((predicate) => predicate(value));
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Requires at least one predicate to pass, checking left to right on each invocation.
|
|
44
|
+
* Stops at the first truthy result. With no predicates, always returns false.
|
|
45
|
+
* Callback exceptions propagate and prevent later checks from running.
|
|
46
|
+
*
|
|
47
|
+
* @template A
|
|
48
|
+
* @param {...Predicate<A>} predicates - Synchronous checks receiving the same input value.
|
|
49
|
+
* @returns {Predicate<A>} The combined predicate.
|
|
50
|
+
* @example
|
|
51
|
+
* const canHeal = or(hero => hero.potions > 0, hero => hero.knowsCure);
|
|
52
|
+
* canHeal({ potions: 0, knowsCure: true }); // true
|
|
53
|
+
*/
|
|
54
|
+
export const or = (...predicates) => (value) =>
|
|
55
|
+
predicates.some((predicate) => predicate(value));
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Adapts a predicate to check a selected value.
|
|
60
|
+
* Each call runs selector, then passes its result to the predicate.
|
|
61
|
+
* Callback exceptions propagate.
|
|
62
|
+
*
|
|
63
|
+
* @template A, B
|
|
64
|
+
* @param {(value: A) => B} selector - Converts the original input into the predicate's input.
|
|
65
|
+
* @returns {(predicate: Predicate<B>) => Predicate<A>}
|
|
66
|
+
* A function adapting a predicate to the original input.
|
|
67
|
+
* @example
|
|
68
|
+
* const canCast = contramap(spell => spell.cost)(cost => cost <= 20);
|
|
69
|
+
* canCast({ name: "Blizzard", cost: 10 }); // true
|
|
70
|
+
*/
|
|
71
|
+
export const contramap = (selector) => (predicate) => (value) =>
|
|
72
|
+
predicate(selector(value));
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import * as Option from "../option/index.js";
|
|
2
|
+
import * as Result from "../result/index.js";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Transforms a finite iterable and collects Some values, skipping None.
|
|
7
|
+
* Returns a new array in input order. Callback throws stop iteration and propagate.
|
|
8
|
+
*
|
|
9
|
+
* @template A, B
|
|
10
|
+
* @param {(value: A) => import("../option/index.js").Option<B>} transform - Called with each element.
|
|
11
|
+
* @returns {(values: Iterable<A>) => B[]} A function collecting present values.
|
|
12
|
+
* @example
|
|
13
|
+
* mapOption(n => n > 0 ? Option.some(n * 2) : Option.none)([1, -1, 3]); // [2, 6]
|
|
14
|
+
*/
|
|
15
|
+
export const mapOption = (transform) => (values) => {
|
|
16
|
+
const outputs = [];
|
|
17
|
+
|
|
18
|
+
for (const value of values) {
|
|
19
|
+
const option = transform(value);
|
|
20
|
+
|
|
21
|
+
if (Option.isSome(option)) {
|
|
22
|
+
outputs.push(option.value);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return outputs;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Transforms a finite iterable and separates Ok values from Err values.
|
|
32
|
+
* Visits every element, preserving input order in both output arrays.
|
|
33
|
+
* Callback throws stop iteration and propagate.
|
|
34
|
+
*
|
|
35
|
+
* @template A, B, E
|
|
36
|
+
* @param {(value: A) => import("../result/index.js").Result<B, E>} transform -
|
|
37
|
+
* Called with each element.
|
|
38
|
+
* @returns {(values: Iterable<A>) => {values: B[], errors: E[]}}
|
|
39
|
+
* A function separating values and errors.
|
|
40
|
+
* @example
|
|
41
|
+
* mapResult(n => n > 0 ? Result.ok(n) : Result.err("Not positive"))([2, -1, 3]);
|
|
42
|
+
* // { values: [2, 3], errors: ["Not positive"] }
|
|
43
|
+
*/
|
|
44
|
+
export const mapResult = (transform) => (values) => {
|
|
45
|
+
const outputs = [];
|
|
46
|
+
const errors = [];
|
|
47
|
+
|
|
48
|
+
for (const value of values) {
|
|
49
|
+
const result = transform(value);
|
|
50
|
+
|
|
51
|
+
Result.isOk(result)
|
|
52
|
+
? outputs.push(result.value)
|
|
53
|
+
: errors.push(result.error);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { values: outputs, errors };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns Some for the first match, or None when no element matches.
|
|
63
|
+
* Stops at the match; a matching undefined is still Some(undefined).
|
|
64
|
+
* Predicate exceptions propagate.
|
|
65
|
+
*
|
|
66
|
+
* @template A
|
|
67
|
+
* @param {(value: A) => boolean} predicate - Called with each element until a match is found.
|
|
68
|
+
* @returns {(values: Iterable<A>) => import("../option/index.js").Option<A>}
|
|
69
|
+
* A function searching for a match.
|
|
70
|
+
* @example
|
|
71
|
+
* findFirst(n => n > 3)([1, 5, 7]); // { type: "some", value: 5 }
|
|
72
|
+
*/
|
|
73
|
+
export const findFirst = (predicate) => (values) => {
|
|
74
|
+
for (const value of values) {
|
|
75
|
+
if (predicate(value)) {
|
|
76
|
+
return Option.some(value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return Option.none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Reads an element by index, counting negative indices from the end.
|
|
86
|
+
* Returns None for an out-of-bounds index. An in-bounds hole or undefined value
|
|
87
|
+
* returns Some(undefined). Validates the index before receiving the array.
|
|
88
|
+
*
|
|
89
|
+
* @param {number} index - A safe integer; -1 selects the last element.
|
|
90
|
+
* @returns {<A>(values: ReadonlyArray<A>) => import("../option/index.js").Option<A | undefined>}
|
|
91
|
+
* A function reading an array element.
|
|
92
|
+
* @throws {RangeError} If index is not a safe integer.
|
|
93
|
+
* @example
|
|
94
|
+
* at(-1)(["Link", "Zelda"]); // { type: "some", value: "Zelda" }
|
|
95
|
+
* at(2)(["Link"]); // { type: "none" }
|
|
96
|
+
*/
|
|
97
|
+
export const at = (index) => {
|
|
98
|
+
if (!Number.isSafeInteger(index)) {
|
|
99
|
+
throw new RangeError("Index must be a safe integer");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return (values) => {
|
|
103
|
+
const position = index < 0 ? values.length + index : index;
|
|
104
|
+
|
|
105
|
+
return position >= 0 && position < values.length
|
|
106
|
+
? Option.some(values[position])
|
|
107
|
+
: Option.none;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Groups a finite iterable into a new Map of new arrays, preserving element references.
|
|
114
|
+
* Keys appear in first-seen order; each group keeps input order. Uses Map key equality,
|
|
115
|
+
* including reference identity for object keys. Callback exceptions propagate.
|
|
116
|
+
*
|
|
117
|
+
* @template A, K
|
|
118
|
+
* @param {(value: A) => K} keyOf - Computes a key from each element.
|
|
119
|
+
* @returns {(values: Iterable<A>) => Map<K, A[]>} A function grouping elements.
|
|
120
|
+
* @example
|
|
121
|
+
* groupBy(name => name.length)(["Link", "Zelda", "Navi"]);
|
|
122
|
+
* // new Map([[4, ["Link", "Navi"]], [5, ["Zelda"]]])
|
|
123
|
+
*/
|
|
124
|
+
export const groupBy = (keyOf) => (values) => {
|
|
125
|
+
const outputs = new Map();
|
|
126
|
+
|
|
127
|
+
for (const value of values) {
|
|
128
|
+
const key = keyOf(value)
|
|
129
|
+
|
|
130
|
+
outputs.has(key)
|
|
131
|
+
? outputs.get(key).push(value)
|
|
132
|
+
: outputs.set(key, [value])
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return outputs;
|
|
136
|
+
}
|