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,455 @@
|
|
|
1
|
+
import * as Option from "../option/index.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A successful value.
|
|
5
|
+
*
|
|
6
|
+
* @template A
|
|
7
|
+
* @typedef {Object} Ok
|
|
8
|
+
* @property {"ok"} type - Identifies success.
|
|
9
|
+
* @property {A} value - The successful payload.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A failure value. Its payload need not be an Error instance.
|
|
14
|
+
*
|
|
15
|
+
* @template E
|
|
16
|
+
* @typedef {Object} Err
|
|
17
|
+
* @property {"err"} type - Identifies failure.
|
|
18
|
+
* @property {E} error - The failure payload.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The outcome of an operation that may succeed or fail.
|
|
23
|
+
*
|
|
24
|
+
* @template A, E
|
|
25
|
+
* @typedef {Ok<A> | Err<E>} Result
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Wraps a successful value without inspecting, cloning, or awaiting it.
|
|
31
|
+
*
|
|
32
|
+
* @template A
|
|
33
|
+
* @param {A} value - The successful payload, including null or undefined.
|
|
34
|
+
* @returns {Ok<A>} A new Ok.
|
|
35
|
+
* @example
|
|
36
|
+
* ok("Link"); // { type: "ok", value: "Link" }
|
|
37
|
+
*/
|
|
38
|
+
export const ok = (value) => ({ type: "ok", value: value });
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Wraps a failure payload without inspecting or cloning it.
|
|
43
|
+
*
|
|
44
|
+
* @template E
|
|
45
|
+
* @param {E} error - Any failure value; an Error instance is not required.
|
|
46
|
+
* @returns {Err<E>} A new Err.
|
|
47
|
+
* @example
|
|
48
|
+
* err("No stamina"); // { type: "err", error: "No stamina" }
|
|
49
|
+
*/
|
|
50
|
+
export const err = (error) => ({ type: "err", error: error });
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Checks whether a Result is Ok, regardless of its payload's truthiness.
|
|
55
|
+
*
|
|
56
|
+
* @template A, E
|
|
57
|
+
* @param {Result<A, E>} result - The Result to inspect, not an arbitrary value.
|
|
58
|
+
* @returns {result is Ok<A>} Whether the result has the Ok tag.
|
|
59
|
+
* @example
|
|
60
|
+
* isOk(ok(undefined)); // true
|
|
61
|
+
*/
|
|
62
|
+
export const isOk = (result) => result.type === "ok";
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Checks whether a Result is Err, regardless of its payload's truthiness.
|
|
67
|
+
*
|
|
68
|
+
* @template A, E
|
|
69
|
+
* @param {Result<A, E>} result - The Result to inspect, not an arbitrary value.
|
|
70
|
+
* @returns {result is Err<E>} Whether the result has the Err tag.
|
|
71
|
+
* @example
|
|
72
|
+
* isErr(err(false)); // true
|
|
73
|
+
*/
|
|
74
|
+
export const isErr = (result) => result.type === "err";
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Wraps a non-nullish value in Ok or builds Err for null and undefined.
|
|
79
|
+
* The error callback runs only for a missing value. Exceptions propagate.
|
|
80
|
+
*
|
|
81
|
+
* @template E
|
|
82
|
+
* @param {(value: null | undefined) => E} onNull - Receives the original null or undefined.
|
|
83
|
+
* @returns {<A>(value: A) => Result<NonNullable<A>, E>} A function wrapping a possibly missing value.
|
|
84
|
+
* @example
|
|
85
|
+
* fromNullable(() => "Missing hero")(null); // { type: "err", error: "Missing hero" }
|
|
86
|
+
*/
|
|
87
|
+
export const fromNullable = (onNull) => (value) =>
|
|
88
|
+
value !== null && value !== undefined ? ok(value) : err(onNull(value));
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns Ok when the predicate passes, otherwise Err from onFalse.
|
|
93
|
+
* Tests the input even when it is undefined. Callback exceptions propagate.
|
|
94
|
+
*
|
|
95
|
+
* @template A, E
|
|
96
|
+
* @param {Object} options - The check and its failure handler.
|
|
97
|
+
* @param {(value: A) => boolean} options.predicate - Called once with the input value.
|
|
98
|
+
* @param {(value: A) => E} options.onFalse - Receives the same value only when the check fails.
|
|
99
|
+
* @returns {(value: A) => Result<A, E>} A function testing and wrapping a value.
|
|
100
|
+
* @example
|
|
101
|
+
* const alive = fromPredicate({ predicate: health => health > 0, onFalse: () => "Defeated" });
|
|
102
|
+
* alive(0); // { type: "err", error: "Defeated" }
|
|
103
|
+
*/
|
|
104
|
+
export const fromPredicate = ({ predicate, onFalse }) => (value) =>
|
|
105
|
+
predicate(value) ? ok(value) : err(onFalse(value));
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Converts Some to Ok or computes an error for None.
|
|
110
|
+
* Some payloads are preserved, including null and undefined. Exceptions propagate.
|
|
111
|
+
*
|
|
112
|
+
* @template E
|
|
113
|
+
* @param {() => E} onNone - Called without arguments only for None.
|
|
114
|
+
* @returns {<A>(option: import("../option/index.js").Option<A>) => Result<A, E>}
|
|
115
|
+
* A function converting an Option.
|
|
116
|
+
* @example
|
|
117
|
+
* fromOption(() => "Missing hero")(Option.none); // { type: "err", error: "Missing hero" }
|
|
118
|
+
*/
|
|
119
|
+
export const fromOption = (onNone) => (option) =>
|
|
120
|
+
Option.isSome(option) ? ok(option.value) : err(onNone());
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Runs a function immediately, wrapping its return value in Ok or a throw in Err.
|
|
125
|
+
* Preserves thrown values exactly. Does not await promises or catch rejections;
|
|
126
|
+
* use TaskResult.fromThrowable for asynchronous operations.
|
|
127
|
+
*
|
|
128
|
+
* @template A
|
|
129
|
+
* @param {() => A} func - Called once without arguments, immediately.
|
|
130
|
+
* @returns {Result<A, unknown>} Ok with the return value, or Err with the thrown value.
|
|
131
|
+
* @example
|
|
132
|
+
* fromThrowable(() => JSON.parse('"Link"')); // { type: "ok", value: "Link" }
|
|
133
|
+
* fromThrowable(() => { throw "No stamina"; }); // { type: "err", error: "No stamina" }
|
|
134
|
+
*/
|
|
135
|
+
export const fromThrowable = (func) => {
|
|
136
|
+
try {
|
|
137
|
+
return ok(func());
|
|
138
|
+
} catch (error) {
|
|
139
|
+
return err(error);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Transforms an Ok value; preserves Err. Wraps the output without flattening
|
|
146
|
+
* or awaiting it. Callback exceptions propagate.
|
|
147
|
+
*
|
|
148
|
+
* @template A, B
|
|
149
|
+
* @param {(value: A) => B} transform - Receives the entire Ok payload.
|
|
150
|
+
* @returns {<E>(result: Result<A, E>) => Result<B, E>} A function mapping Ok values.
|
|
151
|
+
* @example
|
|
152
|
+
* map(health => health + 1)(ok(3)); // { type: "ok", value: 4 }
|
|
153
|
+
* map(health => ok(health + 1))(ok(3));
|
|
154
|
+
* // { type: "ok", value: { type: "ok", value: 4 } }
|
|
155
|
+
*/
|
|
156
|
+
export const map = (transform) => (result) =>
|
|
157
|
+
isOk(result) ? ok(transform(result.value)) : result;
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Transforms an Err value; preserves Ok.
|
|
162
|
+
* Callback exceptions propagate instead of becoming Err.
|
|
163
|
+
*
|
|
164
|
+
* @template E, F
|
|
165
|
+
* @param {(error: E) => F} transform - Receives the original error payload.
|
|
166
|
+
* @returns {<A>(result: Result<A, E>) => Result<A, F>} A function mapping Err values.
|
|
167
|
+
* @example
|
|
168
|
+
* mapError(message => `Failed: ${message}`)(err("No stamina"));
|
|
169
|
+
* // { type: "err", error: "Failed: No stamina" }
|
|
170
|
+
*/
|
|
171
|
+
export const mapError = (transform) => (result) =>
|
|
172
|
+
isErr(result) ? err(transform(result.error)) : result;
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Transforms the matching branch, keeping its Ok or Err tag.
|
|
177
|
+
* Wraps the callback's output without flattening or awaiting it. Exceptions propagate.
|
|
178
|
+
*
|
|
179
|
+
* @template A, B, E, F
|
|
180
|
+
* @param {Object} handlers - A transform for each branch.
|
|
181
|
+
* @param {(value: A) => B} handlers.onOk - Called with the successful payload for Ok.
|
|
182
|
+
* @param {(error: E) => F} handlers.onErr - Called with the error payload for Err.
|
|
183
|
+
* @returns {(result: Result<A, E>) => Result<B, F>} A function mapping either branch.
|
|
184
|
+
* @example
|
|
185
|
+
* mapBoth({ onOk: health => health + 1, onErr: String })(ok(3));
|
|
186
|
+
* // { type: "ok", value: 4 }
|
|
187
|
+
*/
|
|
188
|
+
export const mapBoth = ({ onOk, onErr }) => (result) =>
|
|
189
|
+
isOk(result) ? ok(onOk(result.value)) : err(onErr(result.error));
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Chains an Ok value into another Result; preserves an existing Err.
|
|
194
|
+
* Returns the callback's Result directly. Callback exceptions propagate.
|
|
195
|
+
*
|
|
196
|
+
* @template A, B, F
|
|
197
|
+
* @param {(value: A) => Result<B, F>} transform - Receives the Ok payload and returns a Result.
|
|
198
|
+
* @returns {<E>(result: Result<A, E>) => Result<B, E | F>} A function chaining Results.
|
|
199
|
+
* @example
|
|
200
|
+
* flatMap(health => health > 0 ? ok(health) : err("Defeated"))(ok(0));
|
|
201
|
+
* // { type: "err", error: "Defeated" }
|
|
202
|
+
*/
|
|
203
|
+
export const flatMap = (transform) => (result) =>
|
|
204
|
+
isOk(result) ? transform(result.value) : result;
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Transforms Ok into an Option, converting Some to Ok and None to a computed Err.
|
|
210
|
+
* Preserves an existing Err and skips both callbacks. Exceptions propagate.
|
|
211
|
+
*
|
|
212
|
+
* @template A, B, F
|
|
213
|
+
* @param {Object} options - The Option-producing transform and missing-value handler.
|
|
214
|
+
* @param {(value: A) => import("../option/index.js").Option<B>} options.transform -
|
|
215
|
+
* Receives the Ok payload and returns an Option.
|
|
216
|
+
* @param {(value: A) => F} options.onNone - Receives the original Ok payload if the Option is None.
|
|
217
|
+
* @returns {<E>(result: Result<A, E>) => Result<B, E | F>} A function chaining an Option operation.
|
|
218
|
+
* @example
|
|
219
|
+
* const heroName = flatMapOption({
|
|
220
|
+
* transform: hero => Option.fromNullable(hero.name),
|
|
221
|
+
* onNone: () => "Missing name",
|
|
222
|
+
* });
|
|
223
|
+
* heroName(ok({})); // { type: "err", error: "Missing name" }
|
|
224
|
+
*/
|
|
225
|
+
export const flatMapOption = ({ transform, onNone }) => (result) => {
|
|
226
|
+
if (isErr(result)) {
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const option = transform(result.value);
|
|
231
|
+
|
|
232
|
+
return Option.isSome(option) ? ok(option.value) : err(onNone(result.value));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Removes one nested Result layer, preserving either failure payload.
|
|
238
|
+
*
|
|
239
|
+
* @template A, E, F
|
|
240
|
+
* @param {Result<Result<A, F>, E>} result - A Result whose Ok payload is another Result.
|
|
241
|
+
* @returns {Result<A, E | F>} The inner Result, or the original outer Err.
|
|
242
|
+
* @example
|
|
243
|
+
* flatten(ok(ok("Link"))); // { type: "ok", value: "Link" }
|
|
244
|
+
*/
|
|
245
|
+
export const flatten = (result) =>
|
|
246
|
+
isOk(result) ? result.value : result;
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Keeps a passing Ok or replaces it with Err from onFalse.
|
|
251
|
+
* Preserves an existing Err and skips both callbacks. Exceptions propagate.
|
|
252
|
+
*
|
|
253
|
+
* @template A, F
|
|
254
|
+
* @param {Object} options - The check and its failure handler.
|
|
255
|
+
* @param {(value: A) => boolean} options.predicate - Called once with the Ok payload.
|
|
256
|
+
* @param {(value: A) => F} options.onFalse - Receives that payload only when the check fails.
|
|
257
|
+
* @returns {<E>(result: Result<A, E>) => Result<A, E | F>} A function filtering Ok values.
|
|
258
|
+
* @example
|
|
259
|
+
* const alive = filterOrElse({ predicate: health => health > 0, onFalse: () => "Defeated" });
|
|
260
|
+
* alive(ok(0)); // { type: "err", error: "Defeated" }
|
|
261
|
+
*/
|
|
262
|
+
export const filterOrElse = ({ predicate, onFalse }) => (result) => {
|
|
263
|
+
if (isErr(result) || predicate(result.value)) {
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return err(onFalse(result.value));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Recovers from Err using another Result; preserves an existing Ok.
|
|
273
|
+
* Callback exceptions propagate.
|
|
274
|
+
*
|
|
275
|
+
* @template B, E, F
|
|
276
|
+
* @param {(error: E) => Result<B, F>} fallback - Called only for Err, with its error payload.
|
|
277
|
+
* @returns {<A>(result: Result<A, E>) => Result<A | B, F>} A function recovering from Err.
|
|
278
|
+
* @example
|
|
279
|
+
* orElse(() => ok("Link"))(err("Missing hero")); // { type: "ok", value: "Link" }
|
|
280
|
+
*/
|
|
281
|
+
export const orElse = (fallback) => (result) =>
|
|
282
|
+
isErr(result) ? fallback(result.error) : result;
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Runs an effect for Ok and returns the original Result; skips the effect for Err.
|
|
287
|
+
* The effect's result is ignored, promises are not awaited, and synchronous
|
|
288
|
+
* exceptions propagate.
|
|
289
|
+
*
|
|
290
|
+
* @template A
|
|
291
|
+
* @param {(value: A) => unknown} effect - Observes the Ok value.
|
|
292
|
+
* @returns {<E>(result: Result<A, E>) => Result<A, E>} A function observing Ok values.
|
|
293
|
+
* @example
|
|
294
|
+
* const seen = [];
|
|
295
|
+
* tap(name => { seen.push(name); })(ok("Link"));
|
|
296
|
+
* seen; // ["Link"]
|
|
297
|
+
*/
|
|
298
|
+
export const tap = (effect) => (result) => {
|
|
299
|
+
if (isOk(result)) {
|
|
300
|
+
effect(result.value);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Runs an effect for Err and returns the original Result; skips the effect for Ok.
|
|
309
|
+
* The effect's result is ignored, promises are not awaited, and synchronous
|
|
310
|
+
* exceptions propagate.
|
|
311
|
+
*
|
|
312
|
+
* @template E
|
|
313
|
+
* @param {(error: E) => unknown} effect - Observes the Err value.
|
|
314
|
+
* @returns {<A>(result: Result<A, E>) => Result<A, E>} A function observing Err values.
|
|
315
|
+
* @example
|
|
316
|
+
* const errors = [];
|
|
317
|
+
* tapError(message => { errors.push(message); })(err("No stamina"));
|
|
318
|
+
* errors; // ["No stamina"]
|
|
319
|
+
*/
|
|
320
|
+
export const tapError = (effect) => (result) => {
|
|
321
|
+
if (isErr(result)) {
|
|
322
|
+
effect(result.error);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Unwraps Ok or computes a fallback from Err. Callback exceptions propagate.
|
|
331
|
+
*
|
|
332
|
+
* @template B, E
|
|
333
|
+
* @param {(error: E) => B} fallback - Called only for Err, with its error payload.
|
|
334
|
+
* @returns {<A>(result: Result<A, E>) => A | B} A function unwrapping a Result.
|
|
335
|
+
* @example
|
|
336
|
+
* getOrElse(() => "Link")(err("Missing hero")); // "Link"
|
|
337
|
+
*/
|
|
338
|
+
export const getOrElse = (fallback) => (result) =>
|
|
339
|
+
isOk(result) ? result.value : fallback(result.error);
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Unwraps Ok or throws the original Err payload without converting it to an Error.
|
|
344
|
+
*
|
|
345
|
+
* @template A, E
|
|
346
|
+
* @param {Result<A, E>} result - The Result to unwrap.
|
|
347
|
+
* @returns {A} The successful payload.
|
|
348
|
+
* @throws {*} The original Err payload, including non-Error values.
|
|
349
|
+
* @example
|
|
350
|
+
* getOrThrow(ok("Link")); // "Link"
|
|
351
|
+
*/
|
|
352
|
+
export const getOrThrow = (result) => {
|
|
353
|
+
if (isErr(result)) {
|
|
354
|
+
throw result.error;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return result.value;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Calls the matching handler and returns its output directly.
|
|
363
|
+
* Callback exceptions propagate.
|
|
364
|
+
*
|
|
365
|
+
* @template A, B, E, C
|
|
366
|
+
* @param {Object} handlers - Callbacks for the two possible branches.
|
|
367
|
+
* @param {(value: A) => B} handlers.onOk - Called with the successful payload for Ok.
|
|
368
|
+
* @param {(error: E) => C} handlers.onErr - Called with the error payload for Err.
|
|
369
|
+
* @returns {(result: Result<A, E>) => B | C} A function handling either branch.
|
|
370
|
+
* @example
|
|
371
|
+
* const greet = match({ onOk: name => `Hello, ${name}`, onErr: message => message });
|
|
372
|
+
* greet(ok("Link")); // "Hello, Link"
|
|
373
|
+
*/
|
|
374
|
+
export const match = ({ onOk, onErr }) => (result) =>
|
|
375
|
+
isOk(result) ? onOk(result.value) : onErr(result.error);
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Combines two Ok payloads or returns the first Err, checking the left input first.
|
|
380
|
+
* Calls the combiner only when both inputs are Ok. Exceptions propagate.
|
|
381
|
+
*
|
|
382
|
+
* @template A, B, C
|
|
383
|
+
* @param {(left: A, right: B) => C} combine - Combines the two Ok values.
|
|
384
|
+
* @returns {<E, F>(left: Result<A, E>, right: Result<B, F>) => Result<C, E | F>}
|
|
385
|
+
* A function combining two Results.
|
|
386
|
+
* @example
|
|
387
|
+
* const add = zipWith((a, b) => a + b);
|
|
388
|
+
* add(ok(2), ok(3)); // { type: "ok", value: 5 }
|
|
389
|
+
* add(err(["Left missing"]), err(["Right missing"]));
|
|
390
|
+
* // { type: "err", error: ["Left missing"] }
|
|
391
|
+
*/
|
|
392
|
+
export const zipWith = (combine) => (left, right) => {
|
|
393
|
+
for (const result of [left, right]) {
|
|
394
|
+
if (isErr(result)) {
|
|
395
|
+
return result;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return ok(combine(left.value, right.value));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Collects Ok payloads in iteration order, stopping at the first Err.
|
|
405
|
+
* Empty input returns Ok containing an empty array.
|
|
406
|
+
*
|
|
407
|
+
* @template A, E
|
|
408
|
+
* @param {Iterable<Result<A, E>>} results - A finite iterable of Results to collect.
|
|
409
|
+
* @returns {Result<A[], E>} Ok containing a new array, or the original first Err.
|
|
410
|
+
* @example
|
|
411
|
+
* all([ok("Link"), ok("Zelda")]); // { type: "ok", value: ["Link", "Zelda"] }
|
|
412
|
+
* all([err(["Name required"]), err(["Age required"])]);
|
|
413
|
+
* // { type: "err", error: ["Name required"] }
|
|
414
|
+
*/
|
|
415
|
+
export const all = (results) => {
|
|
416
|
+
const outputs = [];
|
|
417
|
+
|
|
418
|
+
for (const result of results) {
|
|
419
|
+
if (isErr(result)) {
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
outputs.push(result.value);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return ok(outputs);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Transforms values in iteration order and collects their Ok payloads.
|
|
432
|
+
* Stops at the first Err without transforming later inputs. Exceptions propagate.
|
|
433
|
+
* Empty input returns Ok containing an empty array.
|
|
434
|
+
*
|
|
435
|
+
* @template A, B, E
|
|
436
|
+
* @param {(value: A) => Result<B, E>} transform - Called once per visited value, with no index.
|
|
437
|
+
* @returns {(values: Iterable<A>) => Result<B[], E>} A function collecting transformed values.
|
|
438
|
+
* @example
|
|
439
|
+
* traverse(health => ok(health + 1))([2, 3]); // { type: "ok", value: [3, 4] }
|
|
440
|
+
*/
|
|
441
|
+
export const traverse = (transform) => (values) => {
|
|
442
|
+
const outputs = []
|
|
443
|
+
|
|
444
|
+
for (const value of values) {
|
|
445
|
+
const result = transform(value)
|
|
446
|
+
|
|
447
|
+
if (isErr(result)) {
|
|
448
|
+
return result;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
outputs.push(result.value);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return ok(outputs);
|
|
455
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An associative operation for combining two values of the same type.
|
|
3
|
+
* Custom combiners should preserve input values. Associativity means grouping
|
|
4
|
+
* combinations differently produces the same result; JavaScript number rounding
|
|
5
|
+
* can limit this property for the numeric helpers.
|
|
6
|
+
*
|
|
7
|
+
* @template A
|
|
8
|
+
* @typedef {Object} Semigroup
|
|
9
|
+
* @property {(left: A, right: A) => A} combine - Combines the left and right values in that order.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Combines numbers by addition, using JavaScript number arithmetic.
|
|
14
|
+
*
|
|
15
|
+
* @type {Semigroup<number>}
|
|
16
|
+
* @example
|
|
17
|
+
* sum.combine(2, 3); // 5
|
|
18
|
+
*/
|
|
19
|
+
export const sum = {
|
|
20
|
+
combine: (left, right) => left + right,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Combines numbers by multiplication, using JavaScript number arithmetic.
|
|
26
|
+
*
|
|
27
|
+
* @type {Semigroup<number>}
|
|
28
|
+
* @example
|
|
29
|
+
* product.combine(2, 3); // 6
|
|
30
|
+
*/
|
|
31
|
+
export const product = {
|
|
32
|
+
combine: (left, right) => left * right,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Concatenates strings in left-to-right order without a separator.
|
|
38
|
+
*
|
|
39
|
+
* @type {Semigroup<string>}
|
|
40
|
+
* @example
|
|
41
|
+
* string.combine("Hello, ", "Link"); // "Hello, Link"
|
|
42
|
+
*/
|
|
43
|
+
export const string = {
|
|
44
|
+
combine: (left, right) => left + right,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Concatenates arrays into a new array without changing either input.
|
|
50
|
+
* Elements retain their references; nested values are not cloned.
|
|
51
|
+
*
|
|
52
|
+
* @type {{combine: <A, B>(left: ReadonlyArray<A>, right: ReadonlyArray<B>) => (A | B)[]}}
|
|
53
|
+
* @example
|
|
54
|
+
* array.combine(["Link"], ["Zelda"]); // ["Link", "Zelda"]
|
|
55
|
+
*/
|
|
56
|
+
export const array = {
|
|
57
|
+
combine: (left, right) => [...left, ...right],
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Creates a semigroup that combines matching fields into a new object.
|
|
63
|
+
* Captures the schema's own enumerable string keys and semigroup references at creation.
|
|
64
|
+
* Ignores extra input fields. Field combiners run in schema order; exceptions propagate.
|
|
65
|
+
*
|
|
66
|
+
* @template {Record<string, any>} A
|
|
67
|
+
* @param {{[K in keyof A]: Semigroup<A[K]>}} fields - A semigroup for each field to combine.
|
|
68
|
+
* @returns {Semigroup<A>} The record semigroup.
|
|
69
|
+
* @example
|
|
70
|
+
* const stats = struct({ health: sum, inventory: array });
|
|
71
|
+
* stats.combine({ health: 3, inventory: ["Bow"] }, { health: 2, inventory: ["Shield"] });
|
|
72
|
+
* // { health: 5, inventory: ["Bow", "Shield"] }
|
|
73
|
+
*/
|
|
74
|
+
export const struct = (fields) => {
|
|
75
|
+
const entries = Object.entries(fields);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
combine: (left, right) => Object.fromEntries(
|
|
79
|
+
entries.map(([key, semigroup])=> [
|
|
80
|
+
key,
|
|
81
|
+
semigroup.combine(left[key], right[key])
|
|
82
|
+
])
|
|
83
|
+
),
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Combines a finite iterable left to right using combine(accumulator, value).
|
|
90
|
+
* Each call starts from the same initial value; empty input returns it unchanged.
|
|
91
|
+
* The initial value is not cloned, so a mutating combiner can affect later calls.
|
|
92
|
+
* Iteration and combiner exceptions propagate.
|
|
93
|
+
*
|
|
94
|
+
* @template A
|
|
95
|
+
* @param {Object} options - The combination operation and starting value.
|
|
96
|
+
* @param {Semigroup<A>} options.semigroup - Combines the accumulator with each element.
|
|
97
|
+
* @param {A} options.initial - The starting value.
|
|
98
|
+
* @returns {(values: Iterable<A>) => A} A function combining a finite iterable.
|
|
99
|
+
* @example
|
|
100
|
+
* const total = combineMany({ semigroup: sum, initial: 10 });
|
|
101
|
+
* total([2, 3]); // 15
|
|
102
|
+
* total([]); // 10
|
|
103
|
+
*/
|
|
104
|
+
export const combineMany = ({ semigroup, initial } ) => (values) => {
|
|
105
|
+
let accumulator = initial;
|
|
106
|
+
|
|
107
|
+
for (const value of values) {
|
|
108
|
+
accumulator = semigroup.combine(accumulator, value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return accumulator;
|
|
112
|
+
}
|