@nunofyobiz/effect-extras 0.0.1
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 +140 -0
- package/dist/index.d.ts +3703 -0
- package/dist/index.js +1006 -0
- package/dist/index.js.map +1 -0
- package/package.json +103 -0
- package/src/ArrayX/ArrayX.ts +818 -0
- package/src/ArrayX/index.ts +1 -0
- package/src/BigIntX/BigIntX.ts +35 -0
- package/src/BigIntX/index.ts +1 -0
- package/src/BooleanX/BooleanX.ts +24 -0
- package/src/BooleanX/index.ts +1 -0
- package/src/DurationX/DurationX.ts +178 -0
- package/src/DurationX/index.ts +1 -0
- package/src/EffectX/EffectX.ts +183 -0
- package/src/EffectX/index.ts +1 -0
- package/src/FormDataX/FormDataX.ts +57 -0
- package/src/FormDataX/index.ts +1 -0
- package/src/MapX/MapX.ts +54 -0
- package/src/MapX/index.ts +1 -0
- package/src/NonNullableX/NonNullableX.ts +290 -0
- package/src/NonNullableX/index.ts +2 -0
- package/src/NumberX/NumberX.ts +282 -0
- package/src/NumberX/index.ts +1 -0
- package/src/OptionX/OptionX.ts +234 -0
- package/src/OptionX/index.ts +1 -0
- package/src/OrderX/OrderX.ts +35 -0
- package/src/OrderX/index.ts +1 -0
- package/src/PredicateX/PredicateX.ts +98 -0
- package/src/PredicateX/index.ts +1 -0
- package/src/PromiseX/PromiseX.ts +32 -0
- package/src/PromiseX/index.ts +1 -0
- package/src/RecordX/RecordX.ts +478 -0
- package/src/RecordX/index.ts +1 -0
- package/src/ResultX/ResultX.ts +53 -0
- package/src/ResultX/index.ts +1 -0
- package/src/SchemaX/SchemaX.ts +324 -0
- package/src/SchemaX/index.ts +1 -0
- package/src/SetX/SetX.ts +160 -0
- package/src/SetX/index.ts +1 -0
- package/src/StringX/StringX.ts +97 -0
- package/src/StringX/index.ts +1 -0
- package/src/StructX/StructX.ts +310 -0
- package/src/StructX/index.ts +1 -0
- package/src/These/These.ts +1173 -0
- package/src/These/index.ts +1 -0
- package/src/index.ts +20 -0
|
@@ -0,0 +1,1173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `These` data type — an inclusive-or carrying a `left`, a `right`, or both at once.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.0.0
|
|
5
|
+
*/
|
|
6
|
+
import { Data, Effect, Option, Predicate, Struct, pipe } from "effect";
|
|
7
|
+
import { constUndefined, identity } from "effect/Function";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A value carrying a left `L`, a right `R`, or both at once — the data type for
|
|
11
|
+
* an "inclusive or".
|
|
12
|
+
*
|
|
13
|
+
* Where `Result<R, L>` models an exclusive choice (success _or_ failure),
|
|
14
|
+
* `These` adds the third case where both sides are present. It is a tagged enum
|
|
15
|
+
* with three constructors: `LeftOnly` (only `left`), `RightOnly` (only `right`),
|
|
16
|
+
* and `LeftAndRight` (both). Reach for it when an operation can produce partial
|
|
17
|
+
* results — e.g. a parse that yields a value _and_ a list of warnings.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
22
|
+
*
|
|
23
|
+
* const both: These.These<number, string> = These.LeftAndRight({
|
|
24
|
+
* left: 1,
|
|
25
|
+
* right: "a"
|
|
26
|
+
* })
|
|
27
|
+
*
|
|
28
|
+
* assert.deepStrictEqual(both._tag, "LeftAndRight")
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @category models
|
|
32
|
+
* @since 0.0.0
|
|
33
|
+
*/
|
|
34
|
+
export type These<L, R> = Data.TaggedEnum<{
|
|
35
|
+
LeftOnly: {
|
|
36
|
+
readonly left: L;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
RightOnly: {
|
|
40
|
+
readonly right: R;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
LeftAndRight: {
|
|
44
|
+
readonly left: L;
|
|
45
|
+
readonly right: R;
|
|
46
|
+
};
|
|
47
|
+
}>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The `LeftOnly` member of `These` — a value that carries only a `left` and no
|
|
51
|
+
* `right`.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
56
|
+
*
|
|
57
|
+
* const value: These.LeftOnly<number> = These.LeftOnly({ left: 1 })
|
|
58
|
+
*
|
|
59
|
+
* assert.deepStrictEqual(value.left, 1)
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @category models
|
|
63
|
+
* @since 0.0.0
|
|
64
|
+
*/
|
|
65
|
+
export type LeftOnly<L> = These<L, never> & {
|
|
66
|
+
_tag: "LeftOnly";
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The `RightOnly` member of `These` — a value that carries only a `right` and no
|
|
71
|
+
* `left`.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
76
|
+
*
|
|
77
|
+
* const value: These.RightOnly<string> = These.RightOnly({ right: "a" })
|
|
78
|
+
*
|
|
79
|
+
* assert.deepStrictEqual(value.right, "a")
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @category models
|
|
83
|
+
* @since 0.0.0
|
|
84
|
+
*/
|
|
85
|
+
export type RightOnly<R> = These<never, R> & {
|
|
86
|
+
_tag: "RightOnly";
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The `LeftAndRight` member of `These` — a value that carries both a `left` and a
|
|
91
|
+
* `right`.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
96
|
+
*
|
|
97
|
+
* const value: These.LeftAndRight<number, string> = These.LeftAndRight({
|
|
98
|
+
* left: 1,
|
|
99
|
+
* right: "a"
|
|
100
|
+
* })
|
|
101
|
+
*
|
|
102
|
+
* assert.deepStrictEqual(value, { _tag: "LeftAndRight", left: 1, right: "a" })
|
|
103
|
+
* ```
|
|
104
|
+
*
|
|
105
|
+
* @category models
|
|
106
|
+
* @since 0.0.0
|
|
107
|
+
*/
|
|
108
|
+
export type LeftAndRight<L, R> = These<L, R> & {
|
|
109
|
+
_tag: "LeftAndRight";
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Any `These` that is guaranteed to carry a `left` — either `LeftOnly` or
|
|
114
|
+
* `LeftAndRight`.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
119
|
+
*
|
|
120
|
+
* const value: These.WithLeft<number, string> = These.LeftOnly({ left: 1 })
|
|
121
|
+
*
|
|
122
|
+
* assert.deepStrictEqual(value.left, 1)
|
|
123
|
+
* ```
|
|
124
|
+
*
|
|
125
|
+
* @category models
|
|
126
|
+
* @since 0.0.0
|
|
127
|
+
*/
|
|
128
|
+
export type WithLeft<L, R> = LeftOnly<L> | LeftAndRight<L, R>;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Any `These` that is guaranteed to carry a `right` — either `RightOnly` or
|
|
132
|
+
* `LeftAndRight`.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```ts
|
|
136
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
137
|
+
*
|
|
138
|
+
* const value: These.WithRight<number, string> = These.RightOnly({ right: "a" })
|
|
139
|
+
*
|
|
140
|
+
* assert.deepStrictEqual(value.right, "a")
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* @category models
|
|
144
|
+
* @since 0.0.0
|
|
145
|
+
*/
|
|
146
|
+
export type WithRight<L, R> = RightOnly<R> | LeftAndRight<L, R>;
|
|
147
|
+
|
|
148
|
+
interface TheseDefinition extends Data.TaggedEnum.WithGenerics<2> {
|
|
149
|
+
readonly taggedEnum: These<this["A"], this["B"]>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const taggedEnum = Data.taggedEnum<TheseDefinition>();
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Constructs a `LeftOnly` — a `These` that carries only a `left`.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
160
|
+
*
|
|
161
|
+
* const value = These.LeftOnly({ left: 1 })
|
|
162
|
+
*
|
|
163
|
+
* assert.deepStrictEqual(value._tag, "LeftOnly")
|
|
164
|
+
* assert.deepStrictEqual(value.left, 1)
|
|
165
|
+
* ```
|
|
166
|
+
*
|
|
167
|
+
* @category constructors
|
|
168
|
+
* @since 0.0.0
|
|
169
|
+
*/
|
|
170
|
+
export const LeftOnly = taggedEnum.LeftOnly;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Constructs a `RightOnly` — a `These` that carries only a `right`.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
178
|
+
*
|
|
179
|
+
* const value = These.RightOnly({ right: "a" })
|
|
180
|
+
*
|
|
181
|
+
* assert.deepStrictEqual(value._tag, "RightOnly")
|
|
182
|
+
* assert.deepStrictEqual(value.right, "a")
|
|
183
|
+
* ```
|
|
184
|
+
*
|
|
185
|
+
* @category constructors
|
|
186
|
+
* @since 0.0.0
|
|
187
|
+
*/
|
|
188
|
+
export const RightOnly = taggedEnum.RightOnly;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Constructs a `LeftAndRight` — a `These` that carries both a `left` and a
|
|
192
|
+
* `right`.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
197
|
+
*
|
|
198
|
+
* const value = These.LeftAndRight({ left: 1, right: "a" })
|
|
199
|
+
*
|
|
200
|
+
* assert.deepStrictEqual(value._tag, "LeftAndRight")
|
|
201
|
+
* assert.deepStrictEqual(value.left, 1)
|
|
202
|
+
* assert.deepStrictEqual(value.right, "a")
|
|
203
|
+
* ```
|
|
204
|
+
*
|
|
205
|
+
* @category constructors
|
|
206
|
+
* @since 0.0.0
|
|
207
|
+
*/
|
|
208
|
+
export const LeftAndRight = taggedEnum.LeftAndRight;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Builds per-tag refinements for `These`. `is("LeftOnly")` is a type guard that
|
|
212
|
+
* narrows a `These` to its `LeftOnly` member, and likewise for `"RightOnly"` and
|
|
213
|
+
* `"LeftAndRight"`.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```ts
|
|
217
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
218
|
+
*
|
|
219
|
+
* assert.deepStrictEqual(These.is("LeftOnly")(These.LeftOnly({ left: 1 })), true)
|
|
220
|
+
* assert.deepStrictEqual(
|
|
221
|
+
* These.is("LeftOnly")(These.RightOnly({ right: "a" })),
|
|
222
|
+
* false
|
|
223
|
+
* )
|
|
224
|
+
* ```
|
|
225
|
+
*
|
|
226
|
+
* @category guards
|
|
227
|
+
* @since 0.0.0
|
|
228
|
+
*/
|
|
229
|
+
export const is = taggedEnum.$is;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Folds a `These` over its three tags. Provide a handler for `LeftOnly`,
|
|
233
|
+
* `RightOnly`, and `LeftAndRight` and `match` returns a function from a `These`
|
|
234
|
+
* to the handlers' common result type.
|
|
235
|
+
*
|
|
236
|
+
* @example
|
|
237
|
+
* ```ts
|
|
238
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
239
|
+
*
|
|
240
|
+
* const describe = These.match({
|
|
241
|
+
* LeftOnly: ({ left }) => `left ${left}`,
|
|
242
|
+
* RightOnly: ({ right }) => `right ${right}`,
|
|
243
|
+
* LeftAndRight: ({ left, right }) => `both ${left}/${right}`
|
|
244
|
+
* })
|
|
245
|
+
*
|
|
246
|
+
* assert.deepStrictEqual(
|
|
247
|
+
* describe(These.LeftAndRight({ left: 1, right: "a" })),
|
|
248
|
+
* "both 1/a"
|
|
249
|
+
* )
|
|
250
|
+
* ```
|
|
251
|
+
*
|
|
252
|
+
* @category pattern matching
|
|
253
|
+
* @since 0.0.0
|
|
254
|
+
*/
|
|
255
|
+
export const match = taggedEnum.$match;
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Builds a `These` known to carry a `left`, choosing `LeftAndRight` when `right`
|
|
259
|
+
* is present and `LeftOnly` otherwise.
|
|
260
|
+
*
|
|
261
|
+
* Use it when the `left` is mandatory and the `right` is an optional companion:
|
|
262
|
+
* pass an absent (`null`/`undefined`) `right` to get a `LeftOnly`, or a present
|
|
263
|
+
* one to get a `LeftAndRight`. The return type `WithLeft<L, R>` reflects that a
|
|
264
|
+
* `left` is always present.
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```ts
|
|
268
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
269
|
+
*
|
|
270
|
+
* assert.deepStrictEqual(
|
|
271
|
+
* These.WithLeft({ left: 1, right: "a" }),
|
|
272
|
+
* These.LeftAndRight({ left: 1, right: "a" })
|
|
273
|
+
* )
|
|
274
|
+
*
|
|
275
|
+
* assert.deepStrictEqual(
|
|
276
|
+
* These.WithLeft({ left: 1 }),
|
|
277
|
+
* These.LeftOnly({ left: 1 })
|
|
278
|
+
* )
|
|
279
|
+
* ```
|
|
280
|
+
*
|
|
281
|
+
* @category constructors
|
|
282
|
+
* @since 0.0.0
|
|
283
|
+
*/
|
|
284
|
+
export const WithLeft = <L, R>({
|
|
285
|
+
left,
|
|
286
|
+
right,
|
|
287
|
+
}: {
|
|
288
|
+
left: L;
|
|
289
|
+
right?: R | undefined;
|
|
290
|
+
}): WithLeft<L, R> =>
|
|
291
|
+
Predicate.isNotNullish(right)
|
|
292
|
+
? LeftAndRight({ left, right })
|
|
293
|
+
: LeftOnly({ left });
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Builds a `These` known to carry a `right`, choosing `LeftAndRight` when `left`
|
|
297
|
+
* is present and `RightOnly` otherwise.
|
|
298
|
+
*
|
|
299
|
+
* The mirror of `WithLeft`: the `right` is mandatory and the `left` is an
|
|
300
|
+
* optional companion. Pass an absent (`null`/`undefined`) `left` to get a
|
|
301
|
+
* `RightOnly`, or a present one to get a `LeftAndRight`. The return type
|
|
302
|
+
* `WithRight<L, R>` reflects that a `right` is always present.
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* ```ts
|
|
306
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
307
|
+
*
|
|
308
|
+
* assert.deepStrictEqual(
|
|
309
|
+
* These.WithRight({ left: 1, right: "a" }),
|
|
310
|
+
* These.LeftAndRight({ left: 1, right: "a" })
|
|
311
|
+
* )
|
|
312
|
+
*
|
|
313
|
+
* assert.deepStrictEqual(
|
|
314
|
+
* These.WithRight({ right: "a" }),
|
|
315
|
+
* These.RightOnly({ right: "a" })
|
|
316
|
+
* )
|
|
317
|
+
* ```
|
|
318
|
+
*
|
|
319
|
+
* @category constructors
|
|
320
|
+
* @since 0.0.0
|
|
321
|
+
*/
|
|
322
|
+
export const WithRight = <L, R>({
|
|
323
|
+
left,
|
|
324
|
+
right,
|
|
325
|
+
}: {
|
|
326
|
+
left?: L | undefined;
|
|
327
|
+
right: R;
|
|
328
|
+
}): WithRight<L, R> =>
|
|
329
|
+
Predicate.isNotNullish(left)
|
|
330
|
+
? LeftAndRight({ left, right })
|
|
331
|
+
: RightOnly({ right });
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Builds a `These` from a pair of possibly-nullish inputs, wrapping the result in
|
|
335
|
+
* an `Option` so the all-absent case is expressible.
|
|
336
|
+
*
|
|
337
|
+
* Returns `Option.some(LeftAndRight)` when both are present, `Option.some(LeftOnly)`
|
|
338
|
+
* or `Option.some(RightOnly)` when exactly one is present, and `Option.none()`
|
|
339
|
+
* when both are nullish. Use it as the total entry point for turning two optional
|
|
340
|
+
* values into a `These`.
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```ts
|
|
344
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
345
|
+
* import { Option } from "effect"
|
|
346
|
+
*
|
|
347
|
+
* assert.deepStrictEqual(
|
|
348
|
+
* These.optionFromNullables({ left: 1, right: "a" }),
|
|
349
|
+
* Option.some(These.LeftAndRight({ left: 1, right: "a" }))
|
|
350
|
+
* )
|
|
351
|
+
*
|
|
352
|
+
* assert.deepStrictEqual(
|
|
353
|
+
* These.optionFromNullables({ left: 1, right: null }),
|
|
354
|
+
* Option.some(These.LeftOnly({ left: 1 }))
|
|
355
|
+
* )
|
|
356
|
+
*
|
|
357
|
+
* assert.deepStrictEqual(
|
|
358
|
+
* These.optionFromNullables({ left: null, right: undefined }),
|
|
359
|
+
* Option.none()
|
|
360
|
+
* )
|
|
361
|
+
* ```
|
|
362
|
+
*
|
|
363
|
+
* @category constructors
|
|
364
|
+
* @since 0.0.0
|
|
365
|
+
*/
|
|
366
|
+
export const optionFromNullables = <L, R>({
|
|
367
|
+
left,
|
|
368
|
+
right,
|
|
369
|
+
}: {
|
|
370
|
+
left?: L | null | undefined;
|
|
371
|
+
right?: R | null | undefined;
|
|
372
|
+
}): Option.Option<These<L, R>> => {
|
|
373
|
+
if (Predicate.isNotNullish(left) && Predicate.isNotNullish(right)) {
|
|
374
|
+
return Option.some(LeftAndRight({ left, right }));
|
|
375
|
+
}
|
|
376
|
+
if (Predicate.isNotNullish(left)) {
|
|
377
|
+
return Option.some(LeftOnly({ left }));
|
|
378
|
+
}
|
|
379
|
+
if (Predicate.isNotNullish(right)) {
|
|
380
|
+
return Option.some(RightOnly({ right }));
|
|
381
|
+
}
|
|
382
|
+
return Option.none();
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Builds a `These` from a pair of possibly-nullish inputs, falling back to the
|
|
387
|
+
* `orElse` thunk when both are absent.
|
|
388
|
+
*
|
|
389
|
+
* The non-optional companion to `optionFromNullables`: it unwraps the same logic
|
|
390
|
+
* but resolves the all-absent case with `orElse` instead of an `Option`. The
|
|
391
|
+
* default `orElse` throws, so omit it only when at least one side is guaranteed
|
|
392
|
+
* present.
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```ts
|
|
396
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
397
|
+
*
|
|
398
|
+
* assert.deepStrictEqual(
|
|
399
|
+
* These.fromNullables({ left: 1, right: "a" }),
|
|
400
|
+
* These.LeftAndRight({ left: 1, right: "a" })
|
|
401
|
+
* )
|
|
402
|
+
*
|
|
403
|
+
* // Both absent — fall back via orElse instead of throwing
|
|
404
|
+
* assert.deepStrictEqual(
|
|
405
|
+
* These.fromNullables({
|
|
406
|
+
* left: null,
|
|
407
|
+
* right: null,
|
|
408
|
+
* orElse: () => These.LeftOnly({ left: 0 })
|
|
409
|
+
* }),
|
|
410
|
+
* These.LeftOnly({ left: 0 })
|
|
411
|
+
* )
|
|
412
|
+
* ```
|
|
413
|
+
*
|
|
414
|
+
* @category constructors
|
|
415
|
+
* @since 0.0.0
|
|
416
|
+
*/
|
|
417
|
+
export const fromNullables = <L, R>({
|
|
418
|
+
left,
|
|
419
|
+
right,
|
|
420
|
+
orElse = () => {
|
|
421
|
+
throw new Error("Both left and right are nullable");
|
|
422
|
+
},
|
|
423
|
+
}: {
|
|
424
|
+
left?: L | null | undefined;
|
|
425
|
+
right?: R | null | undefined;
|
|
426
|
+
orElse?: () => These<L, R>;
|
|
427
|
+
}): These<L, R> =>
|
|
428
|
+
pipe(optionFromNullables({ left, right }), Option.getOrElse(orElse));
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Folds a `These` from the left's perspective, collapsing the three tags into two
|
|
432
|
+
* handlers.
|
|
433
|
+
*
|
|
434
|
+
* Both `LeftOnly` and `LeftAndRight` carry a `left`, so they route to the `Left`
|
|
435
|
+
* handler; only `RightOnly` lacks a `left` and routes to `RightOnly`. Use it when
|
|
436
|
+
* you care about the `left` value and treat the right-only case as the exception.
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* ```ts
|
|
440
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
441
|
+
*
|
|
442
|
+
* const onLeft = These.matchLeft({
|
|
443
|
+
* Left: (left: number) => `left ${left}`,
|
|
444
|
+
* RightOnly: (right: string) => `right ${right}`
|
|
445
|
+
* })
|
|
446
|
+
*
|
|
447
|
+
* assert.deepStrictEqual(onLeft(These.LeftOnly({ left: 1 })), "left 1")
|
|
448
|
+
* assert.deepStrictEqual(
|
|
449
|
+
* onLeft(These.LeftAndRight({ left: 1, right: "a" })),
|
|
450
|
+
* "left 1"
|
|
451
|
+
* )
|
|
452
|
+
* assert.deepStrictEqual(onLeft(These.RightOnly({ right: "a" })), "right a")
|
|
453
|
+
* ```
|
|
454
|
+
*
|
|
455
|
+
* @category pattern matching
|
|
456
|
+
* @since 0.0.0
|
|
457
|
+
*/
|
|
458
|
+
export const matchLeft =
|
|
459
|
+
<L, R, A>({
|
|
460
|
+
Left,
|
|
461
|
+
RightOnly,
|
|
462
|
+
}: {
|
|
463
|
+
Left: (left: L) => A;
|
|
464
|
+
RightOnly: (right: R) => A;
|
|
465
|
+
}) =>
|
|
466
|
+
(these: These<L, R>): A =>
|
|
467
|
+
pipe(
|
|
468
|
+
these,
|
|
469
|
+
|
|
470
|
+
match({
|
|
471
|
+
LeftOnly: ({ left }) => Left(left),
|
|
472
|
+
RightOnly: ({ right }) => RightOnly(right),
|
|
473
|
+
LeftAndRight: ({ left }) => Left(left),
|
|
474
|
+
}),
|
|
475
|
+
|
|
476
|
+
// Make Typescript happy
|
|
477
|
+
(a) => a as A,
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Folds a `These` from the right's perspective, collapsing the three tags into two
|
|
482
|
+
* handlers.
|
|
483
|
+
*
|
|
484
|
+
* The mirror of `matchLeft`: both `RightOnly` and `LeftAndRight` carry a `right`,
|
|
485
|
+
* so they route to the `Right` handler; only `LeftOnly` lacks a `right` and routes
|
|
486
|
+
* to `LeftOnly`. Use it when you care about the `right` value and treat the
|
|
487
|
+
* left-only case as the exception.
|
|
488
|
+
*
|
|
489
|
+
* @example
|
|
490
|
+
* ```ts
|
|
491
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
492
|
+
*
|
|
493
|
+
* const onRight = These.matchRight({
|
|
494
|
+
* LeftOnly: (left: number) => `left ${left}`,
|
|
495
|
+
* Right: (right: string) => `right ${right}`
|
|
496
|
+
* })
|
|
497
|
+
*
|
|
498
|
+
* assert.deepStrictEqual(onRight(These.RightOnly({ right: "a" })), "right a")
|
|
499
|
+
* assert.deepStrictEqual(
|
|
500
|
+
* onRight(These.LeftAndRight({ left: 1, right: "a" })),
|
|
501
|
+
* "right a"
|
|
502
|
+
* )
|
|
503
|
+
* assert.deepStrictEqual(onRight(These.LeftOnly({ left: 1 })), "left 1")
|
|
504
|
+
* ```
|
|
505
|
+
*
|
|
506
|
+
* @category pattern matching
|
|
507
|
+
* @since 0.0.0
|
|
508
|
+
*/
|
|
509
|
+
export const matchRight =
|
|
510
|
+
<L, R, A>({
|
|
511
|
+
LeftOnly,
|
|
512
|
+
Right,
|
|
513
|
+
}: {
|
|
514
|
+
LeftOnly: (left: L) => A;
|
|
515
|
+
Right: (right: R) => A;
|
|
516
|
+
}) =>
|
|
517
|
+
(these: These<L, R>): A =>
|
|
518
|
+
pipe(
|
|
519
|
+
these,
|
|
520
|
+
|
|
521
|
+
match({
|
|
522
|
+
LeftOnly: ({ left }) => LeftOnly(left),
|
|
523
|
+
RightOnly: ({ right }) => Right(right),
|
|
524
|
+
LeftAndRight: ({ right }) => Right(right),
|
|
525
|
+
}),
|
|
526
|
+
|
|
527
|
+
// Make Typescript happy
|
|
528
|
+
(a) => a as A,
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Completes a `These` into a guaranteed `LeftAndRight` by filling whichever side
|
|
533
|
+
* is missing from the matching `orElse` thunk.
|
|
534
|
+
*
|
|
535
|
+
* A `LeftAndRight` passes through unchanged; a `LeftOnly` gains a `right` from
|
|
536
|
+
* `orElseRight`; a `RightOnly` gains a `left` from `orElseLeft`. Use it to
|
|
537
|
+
* normalise a partial `These` into the both-present shape before reading both
|
|
538
|
+
* sides.
|
|
539
|
+
*
|
|
540
|
+
* @example
|
|
541
|
+
* ```ts
|
|
542
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
543
|
+
*
|
|
544
|
+
* const fill = These.orElse({
|
|
545
|
+
* orElseLeft: () => 0,
|
|
546
|
+
* orElseRight: () => "default"
|
|
547
|
+
* })
|
|
548
|
+
*
|
|
549
|
+
* assert.deepStrictEqual(
|
|
550
|
+
* fill(These.LeftOnly({ left: 1 })),
|
|
551
|
+
* These.LeftAndRight({ left: 1, right: "default" })
|
|
552
|
+
* )
|
|
553
|
+
* assert.deepStrictEqual(
|
|
554
|
+
* fill(These.RightOnly({ right: "a" })),
|
|
555
|
+
* These.LeftAndRight({ left: 0, right: "a" })
|
|
556
|
+
* )
|
|
557
|
+
* ```
|
|
558
|
+
*
|
|
559
|
+
* @category getters
|
|
560
|
+
* @since 0.0.0
|
|
561
|
+
*/
|
|
562
|
+
export const orElse = <L2, R2>({
|
|
563
|
+
orElseLeft,
|
|
564
|
+
orElseRight,
|
|
565
|
+
}: {
|
|
566
|
+
orElseLeft: () => L2;
|
|
567
|
+
orElseRight: () => R2;
|
|
568
|
+
}): (<L, R>(these: These<L, R>) => LeftAndRight<L | L2, R | R2>) =>
|
|
569
|
+
match({
|
|
570
|
+
LeftOnly: ({ left }) => LeftAndRight({ left, right: orElseRight() }),
|
|
571
|
+
RightOnly: ({ right }) => LeftAndRight({ left: orElseLeft(), right }),
|
|
572
|
+
LeftAndRight: ({ left, right }) => LeftAndRight({ left, right }),
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Completes a `These` into a `LeftAndRight` whose missing side is filled with
|
|
577
|
+
* `undefined`.
|
|
578
|
+
*
|
|
579
|
+
* A specialisation of `orElse` that supplies `undefined` for whichever side is
|
|
580
|
+
* absent, so the result always exposes both `left` and `right` keys (each
|
|
581
|
+
* possibly `undefined`). Use it when you want to destructure both sides without
|
|
582
|
+
* branching on the tag.
|
|
583
|
+
*
|
|
584
|
+
* @example
|
|
585
|
+
* ```ts
|
|
586
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
587
|
+
*
|
|
588
|
+
* assert.deepStrictEqual(
|
|
589
|
+
* These.orUndefined(These.LeftOnly({ left: 1 })),
|
|
590
|
+
* These.LeftAndRight({ left: 1, right: undefined })
|
|
591
|
+
* )
|
|
592
|
+
* assert.deepStrictEqual(
|
|
593
|
+
* These.orUndefined(These.RightOnly({ right: "a" })),
|
|
594
|
+
* These.LeftAndRight({ left: undefined, right: "a" })
|
|
595
|
+
* )
|
|
596
|
+
* ```
|
|
597
|
+
*
|
|
598
|
+
* @category getters
|
|
599
|
+
* @since 0.0.0
|
|
600
|
+
*/
|
|
601
|
+
export const orUndefined = orElse({
|
|
602
|
+
orElseLeft: () => undefined,
|
|
603
|
+
orElseRight: () => undefined,
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Extracts the `left` of a `These`, falling back to `orElseReturn` when no `left`
|
|
608
|
+
* is present.
|
|
609
|
+
*
|
|
610
|
+
* `LeftOnly` and `LeftAndRight` return their `left`; `RightOnly` returns the
|
|
611
|
+
* result of `orElseReturn`. Use it to read the left side with a default in one
|
|
612
|
+
* step.
|
|
613
|
+
*
|
|
614
|
+
* @example
|
|
615
|
+
* ```ts
|
|
616
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
617
|
+
*
|
|
618
|
+
* const leftOrZero = These.leftOrElse(() => 0)
|
|
619
|
+
*
|
|
620
|
+
* assert.deepStrictEqual(leftOrZero(These.LeftOnly({ left: 1 })), 1)
|
|
621
|
+
* assert.deepStrictEqual(
|
|
622
|
+
* leftOrZero(These.LeftAndRight({ left: 1, right: "a" })),
|
|
623
|
+
* 1
|
|
624
|
+
* )
|
|
625
|
+
* assert.deepStrictEqual(leftOrZero(These.RightOnly({ right: "a" })), 0)
|
|
626
|
+
* ```
|
|
627
|
+
*
|
|
628
|
+
* @category getters
|
|
629
|
+
* @since 0.0.0
|
|
630
|
+
*/
|
|
631
|
+
export const leftOrElse =
|
|
632
|
+
<A>(orElseReturn: () => A) =>
|
|
633
|
+
<L, R>(these: These<L, R>): L | A =>
|
|
634
|
+
pipe(
|
|
635
|
+
these,
|
|
636
|
+
orElse({
|
|
637
|
+
orElseLeft: orElseReturn,
|
|
638
|
+
orElseRight: constUndefined,
|
|
639
|
+
}),
|
|
640
|
+
Struct.get("left"),
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Extracts the `left` of a `These`, returning `undefined` when no `left` is
|
|
645
|
+
* present.
|
|
646
|
+
*
|
|
647
|
+
* A specialisation of `leftOrElse` whose fallback is `undefined`: `LeftOnly` and
|
|
648
|
+
* `LeftAndRight` yield their `left`, while `RightOnly` yields `undefined`.
|
|
649
|
+
*
|
|
650
|
+
* @example
|
|
651
|
+
* ```ts
|
|
652
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
653
|
+
*
|
|
654
|
+
* assert.deepStrictEqual(These.leftOrUndefined(These.LeftOnly({ left: 1 })), 1)
|
|
655
|
+
* assert.deepStrictEqual(
|
|
656
|
+
* These.leftOrUndefined(These.RightOnly({ right: "a" })),
|
|
657
|
+
* undefined
|
|
658
|
+
* )
|
|
659
|
+
* ```
|
|
660
|
+
*
|
|
661
|
+
* @category getters
|
|
662
|
+
* @since 0.0.0
|
|
663
|
+
*/
|
|
664
|
+
export const leftOrUndefined = leftOrElse(() => undefined);
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Extracts the `right` of a `These`, falling back to `orElseReturn` when no
|
|
668
|
+
* `right` is present.
|
|
669
|
+
*
|
|
670
|
+
* The mirror of `leftOrElse`: `RightOnly` and `LeftAndRight` return their
|
|
671
|
+
* `right`; `LeftOnly` returns the result of `orElseReturn`.
|
|
672
|
+
*
|
|
673
|
+
* @example
|
|
674
|
+
* ```ts
|
|
675
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
676
|
+
*
|
|
677
|
+
* const rightOrDefault = These.rightOrElse(() => "default")
|
|
678
|
+
*
|
|
679
|
+
* assert.deepStrictEqual(
|
|
680
|
+
* rightOrDefault(These.RightOnly({ right: "a" })),
|
|
681
|
+
* "a"
|
|
682
|
+
* )
|
|
683
|
+
* assert.deepStrictEqual(
|
|
684
|
+
* rightOrDefault(These.LeftOnly({ left: 1 })),
|
|
685
|
+
* "default"
|
|
686
|
+
* )
|
|
687
|
+
* ```
|
|
688
|
+
*
|
|
689
|
+
* @category getters
|
|
690
|
+
* @since 0.0.0
|
|
691
|
+
*/
|
|
692
|
+
export const rightOrElse =
|
|
693
|
+
<A>(orElseReturn: () => A) =>
|
|
694
|
+
<L, R>(these: These<L, R>): R | A =>
|
|
695
|
+
pipe(
|
|
696
|
+
these,
|
|
697
|
+
orElse({
|
|
698
|
+
orElseLeft: constUndefined,
|
|
699
|
+
orElseRight: orElseReturn,
|
|
700
|
+
}),
|
|
701
|
+
Struct.get("right"),
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Extracts the `right` of a `These`, returning `undefined` when no `right` is
|
|
706
|
+
* present.
|
|
707
|
+
*
|
|
708
|
+
* A specialisation of `rightOrElse` whose fallback is `undefined`: `RightOnly` and
|
|
709
|
+
* `LeftAndRight` yield their `right`, while `LeftOnly` yields `undefined`.
|
|
710
|
+
*
|
|
711
|
+
* @example
|
|
712
|
+
* ```ts
|
|
713
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
714
|
+
*
|
|
715
|
+
* assert.deepStrictEqual(
|
|
716
|
+
* These.rightOrUndefined(These.RightOnly({ right: "a" })),
|
|
717
|
+
* "a"
|
|
718
|
+
* )
|
|
719
|
+
* assert.deepStrictEqual(
|
|
720
|
+
* These.rightOrUndefined(These.LeftOnly({ left: 1 })),
|
|
721
|
+
* undefined
|
|
722
|
+
* )
|
|
723
|
+
* ```
|
|
724
|
+
*
|
|
725
|
+
* @category getters
|
|
726
|
+
* @since 0.0.0
|
|
727
|
+
*/
|
|
728
|
+
export const rightOrUndefined = rightOrElse(() => undefined);
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Extracts the `right` of a `These` as an `Option`.
|
|
732
|
+
*
|
|
733
|
+
* `RightOnly` and `LeftAndRight` yield `Option.some(right)`; `LeftOnly` yields
|
|
734
|
+
* `Option.none()`. Use it when you want to chain the right side through `Option`
|
|
735
|
+
* combinators rather than fall back to a default eagerly.
|
|
736
|
+
*
|
|
737
|
+
* @example
|
|
738
|
+
* ```ts
|
|
739
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
740
|
+
* import { Option } from "effect"
|
|
741
|
+
*
|
|
742
|
+
* assert.deepStrictEqual(
|
|
743
|
+
* These.rightOption(These.RightOnly({ right: "a" })),
|
|
744
|
+
* Option.some("a")
|
|
745
|
+
* )
|
|
746
|
+
* assert.deepStrictEqual(
|
|
747
|
+
* These.rightOption(These.LeftOnly({ left: 1 })),
|
|
748
|
+
* Option.none()
|
|
749
|
+
* )
|
|
750
|
+
* ```
|
|
751
|
+
*
|
|
752
|
+
* @category getters
|
|
753
|
+
* @since 0.0.0
|
|
754
|
+
*/
|
|
755
|
+
export const rightOption = <L, R>(these: These<L, R>): Option.Option<R> =>
|
|
756
|
+
pipe(
|
|
757
|
+
these,
|
|
758
|
+
matchRight({
|
|
759
|
+
LeftOnly: () => Option.none(),
|
|
760
|
+
Right: Option.some,
|
|
761
|
+
}),
|
|
762
|
+
);
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Extracts the `left` of a `These` as an `Option`.
|
|
766
|
+
*
|
|
767
|
+
* The mirror of `rightOption`: `LeftOnly` and `LeftAndRight` yield
|
|
768
|
+
* `Option.some(left)`, while `RightOnly` yields `Option.none()`.
|
|
769
|
+
*
|
|
770
|
+
* @example
|
|
771
|
+
* ```ts
|
|
772
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
773
|
+
* import { Option } from "effect"
|
|
774
|
+
*
|
|
775
|
+
* assert.deepStrictEqual(
|
|
776
|
+
* These.leftOption(These.LeftOnly({ left: 1 })),
|
|
777
|
+
* Option.some(1)
|
|
778
|
+
* )
|
|
779
|
+
* assert.deepStrictEqual(
|
|
780
|
+
* These.leftOption(These.RightOnly({ right: "a" })),
|
|
781
|
+
* Option.none()
|
|
782
|
+
* )
|
|
783
|
+
* ```
|
|
784
|
+
*
|
|
785
|
+
* @category getters
|
|
786
|
+
* @since 0.0.0
|
|
787
|
+
*/
|
|
788
|
+
export const leftOption = <L, R>(these: These<L, R>): Option.Option<L> =>
|
|
789
|
+
pipe(
|
|
790
|
+
these,
|
|
791
|
+
matchLeft({
|
|
792
|
+
Left: Option.some,
|
|
793
|
+
RightOnly: () => Option.none(),
|
|
794
|
+
}),
|
|
795
|
+
);
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Transforms both sides of a `These`, applying `mapLeft` to any `left` and
|
|
799
|
+
* `mapRight` to any `right`.
|
|
800
|
+
*
|
|
801
|
+
* Each constructor is rebuilt with its mapped contents, so `LeftOnly` maps only
|
|
802
|
+
* the left, `RightOnly` only the right, and `LeftAndRight` both. The tag is
|
|
803
|
+
* preserved. Use it as the bifunctor map over `These`.
|
|
804
|
+
*
|
|
805
|
+
* @example
|
|
806
|
+
* ```ts
|
|
807
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
808
|
+
*
|
|
809
|
+
* const both = These.mapBoth({
|
|
810
|
+
* mapLeft: (left: number) => left + 1,
|
|
811
|
+
* mapRight: (right: string) => right.toUpperCase()
|
|
812
|
+
* })
|
|
813
|
+
*
|
|
814
|
+
* assert.deepStrictEqual(
|
|
815
|
+
* both(These.LeftAndRight({ left: 1, right: "a" })),
|
|
816
|
+
* These.LeftAndRight({ left: 2, right: "A" })
|
|
817
|
+
* )
|
|
818
|
+
* assert.deepStrictEqual(
|
|
819
|
+
* both(These.LeftOnly({ left: 1 })),
|
|
820
|
+
* These.LeftOnly({ left: 2 })
|
|
821
|
+
* )
|
|
822
|
+
* ```
|
|
823
|
+
*
|
|
824
|
+
* @category mapping
|
|
825
|
+
* @since 0.0.0
|
|
826
|
+
*/
|
|
827
|
+
export const mapBoth = <L1, R1, L2, R2>({
|
|
828
|
+
mapLeft,
|
|
829
|
+
mapRight,
|
|
830
|
+
}: {
|
|
831
|
+
mapLeft: (left: L1) => L2;
|
|
832
|
+
mapRight: (right: R1) => R2;
|
|
833
|
+
}): ((these: These<L1, R1>) => These<L2, R2>) =>
|
|
834
|
+
match({
|
|
835
|
+
LeftOnly: ({ left }) => LeftOnly({ left: mapLeft(left) }),
|
|
836
|
+
RightOnly: ({ right }) => RightOnly({ right: mapRight(right) }),
|
|
837
|
+
LeftAndRight: ({ left, right }) =>
|
|
838
|
+
LeftAndRight({ left: mapLeft(left), right: mapRight(right) }),
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Effectful `mapBoth`: transforms each present side through an `Effect`,
|
|
843
|
+
* reassembling the results into a `These` inside an `Effect`.
|
|
844
|
+
*
|
|
845
|
+
* For `LeftAndRight` both effects run via `Effect.all` and their results are
|
|
846
|
+
* combined; `LeftOnly`/`RightOnly` run only the relevant effect. Errors and
|
|
847
|
+
* requirements from both mappers are unioned into the result type. Use it when
|
|
848
|
+
* mapping a `These`'s sides requires effects (validation, IO).
|
|
849
|
+
*
|
|
850
|
+
* @example
|
|
851
|
+
* ```ts
|
|
852
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
853
|
+
* import { Effect } from "effect"
|
|
854
|
+
*
|
|
855
|
+
* const both = These.mapBothEffect({
|
|
856
|
+
* mapLeft: (left: number) => Effect.succeed(left + 1),
|
|
857
|
+
* mapRight: (right: string) => Effect.succeed(right.toUpperCase())
|
|
858
|
+
* })
|
|
859
|
+
*
|
|
860
|
+
* assert.deepStrictEqual(
|
|
861
|
+
* Effect.runSync(both(These.LeftAndRight({ left: 1, right: "a" }))),
|
|
862
|
+
* These.LeftAndRight({ left: 2, right: "A" })
|
|
863
|
+
* )
|
|
864
|
+
* ```
|
|
865
|
+
*
|
|
866
|
+
* @category sequencing
|
|
867
|
+
* @since 0.0.0
|
|
868
|
+
*/
|
|
869
|
+
export const mapBothEffect = <L1, R1, L2, R2, EL, ER, RL, RR>({
|
|
870
|
+
mapLeft,
|
|
871
|
+
mapRight,
|
|
872
|
+
}: {
|
|
873
|
+
mapLeft: (left: L1) => Effect.Effect<L2, EL, RL>;
|
|
874
|
+
mapRight: (right: R1) => Effect.Effect<R2, ER, RR>;
|
|
875
|
+
}): ((
|
|
876
|
+
these: These<L1, R1>,
|
|
877
|
+
) => Effect.Effect<These<L2, R2>, EL | ER, RL | RR>) =>
|
|
878
|
+
match({
|
|
879
|
+
LeftOnly: ({ left }) =>
|
|
880
|
+
pipe(
|
|
881
|
+
mapLeft(left),
|
|
882
|
+
Effect.map((left2) => LeftOnly({ left: left2 })),
|
|
883
|
+
),
|
|
884
|
+
|
|
885
|
+
RightOnly: ({ right }) =>
|
|
886
|
+
pipe(
|
|
887
|
+
mapRight(right),
|
|
888
|
+
Effect.map((right2) => RightOnly({ right: right2 })),
|
|
889
|
+
),
|
|
890
|
+
|
|
891
|
+
LeftAndRight: ({ left, right }) =>
|
|
892
|
+
pipe(
|
|
893
|
+
Effect.all({ left: mapLeft(left), right: mapRight(right) }),
|
|
894
|
+
Effect.map(({ left: left2, right: right2 }) =>
|
|
895
|
+
LeftAndRight({ left: left2, right: right2 }),
|
|
896
|
+
),
|
|
897
|
+
),
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Transforms the `left` of a `These`, leaving any `right` untouched.
|
|
902
|
+
*
|
|
903
|
+
* A specialisation of `mapBoth` with the right mapper set to `identity`:
|
|
904
|
+
* `LeftOnly` and `LeftAndRight` have their `left` mapped, while `RightOnly` passes
|
|
905
|
+
* through unchanged.
|
|
906
|
+
*
|
|
907
|
+
* @example
|
|
908
|
+
* ```ts
|
|
909
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
910
|
+
*
|
|
911
|
+
* const inc = These.mapLeft((left: number) => left + 1)
|
|
912
|
+
*
|
|
913
|
+
* assert.deepStrictEqual(
|
|
914
|
+
* inc(These.LeftAndRight({ left: 1, right: "a" })),
|
|
915
|
+
* These.LeftAndRight({ left: 2, right: "a" })
|
|
916
|
+
* )
|
|
917
|
+
* assert.deepStrictEqual(
|
|
918
|
+
* inc(These.RightOnly({ right: "a" })),
|
|
919
|
+
* These.RightOnly({ right: "a" })
|
|
920
|
+
* )
|
|
921
|
+
* ```
|
|
922
|
+
*
|
|
923
|
+
* @category mapping
|
|
924
|
+
* @since 0.0.0
|
|
925
|
+
*/
|
|
926
|
+
export const mapLeft = <L1, L2>(
|
|
927
|
+
mapLeft: (left: L1) => L2,
|
|
928
|
+
): (<R>(these: These<L1, R>) => These<L2, R>) =>
|
|
929
|
+
mapBoth({ mapLeft, mapRight: identity });
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Chains the `left` of a `These` into a new `These`, flattening the result.
|
|
933
|
+
*
|
|
934
|
+
* Whenever a `left` is present (`LeftOnly` or `LeftAndRight`) it is passed to
|
|
935
|
+
* `mapLeft`, whose returned `These` replaces the original; `RightOnly` passes
|
|
936
|
+
* through unchanged. Use it to sequence left-driven computations that themselves
|
|
937
|
+
* produce a `These`.
|
|
938
|
+
*
|
|
939
|
+
* @example
|
|
940
|
+
* ```ts
|
|
941
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
942
|
+
*
|
|
943
|
+
* const chain = These.flatMapLeft((left: number) =>
|
|
944
|
+
* left > 0
|
|
945
|
+
* ? These.LeftOnly({ left: left * 10 })
|
|
946
|
+
* : These.RightOnly({ right: "non-positive" })
|
|
947
|
+
* )
|
|
948
|
+
*
|
|
949
|
+
* assert.deepStrictEqual(
|
|
950
|
+
* chain(These.LeftOnly({ left: 2 })),
|
|
951
|
+
* These.LeftOnly({ left: 20 })
|
|
952
|
+
* )
|
|
953
|
+
* assert.deepStrictEqual(
|
|
954
|
+
* chain(These.RightOnly({ right: "a" })),
|
|
955
|
+
* These.RightOnly({ right: "a" })
|
|
956
|
+
* )
|
|
957
|
+
* ```
|
|
958
|
+
*
|
|
959
|
+
* @category sequencing
|
|
960
|
+
* @since 0.0.0
|
|
961
|
+
*/
|
|
962
|
+
export const flatMapLeft = <L1, L2, R2>(
|
|
963
|
+
mapLeft: (left: L1) => These<L2, R2>,
|
|
964
|
+
): (<R1>(these: These<L1, R1>) => These<L2, R1 | R2>) =>
|
|
965
|
+
match({
|
|
966
|
+
LeftOnly: ({ left }) => mapLeft(left),
|
|
967
|
+
RightOnly: ({ right }) => RightOnly({ right }),
|
|
968
|
+
LeftAndRight: ({ left }) => mapLeft(left),
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Effectful `mapLeft`: transforms the `left` of a `These` through an `Effect`,
|
|
973
|
+
* leaving any `right` untouched.
|
|
974
|
+
*
|
|
975
|
+
* A specialisation of `mapBothEffect` with the right mapper set to
|
|
976
|
+
* `Effect.succeed`: the `left` (when present) is mapped effectfully and the
|
|
977
|
+
* `right` is carried through unchanged.
|
|
978
|
+
*
|
|
979
|
+
* @example
|
|
980
|
+
* ```ts
|
|
981
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
982
|
+
* import { Effect } from "effect"
|
|
983
|
+
*
|
|
984
|
+
* const inc = These.mapLeftEffect((left: number) => Effect.succeed(left + 1))
|
|
985
|
+
*
|
|
986
|
+
* assert.deepStrictEqual(
|
|
987
|
+
* Effect.runSync(inc(These.LeftAndRight({ left: 1, right: "a" }))),
|
|
988
|
+
* These.LeftAndRight({ left: 2, right: "a" })
|
|
989
|
+
* )
|
|
990
|
+
* ```
|
|
991
|
+
*
|
|
992
|
+
* @category sequencing
|
|
993
|
+
* @since 0.0.0
|
|
994
|
+
*/
|
|
995
|
+
export const mapLeftEffect = <L1, L2, EL, RL>(
|
|
996
|
+
mapLeft: (left: L1) => Effect.Effect<L2, EL, RL>,
|
|
997
|
+
): (<R>(these: These<L1, R>) => Effect.Effect<These<L2, R>, EL, RL>) =>
|
|
998
|
+
mapBothEffect({ mapLeft, mapRight: Effect.succeed });
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Effectful `flatMapLeft`: chains the `left` of a `These` into an `Effect` that
|
|
1002
|
+
* yields a new `These`, flattening the result.
|
|
1003
|
+
*
|
|
1004
|
+
* When a `left` is present it is passed to `mapLeft`, whose effectful `These`
|
|
1005
|
+
* replaces the original; `RightOnly` is lifted unchanged via `Effect.succeed`. Use
|
|
1006
|
+
* it to sequence left-driven effectful computations that produce a `These`.
|
|
1007
|
+
*
|
|
1008
|
+
* @example
|
|
1009
|
+
* ```ts
|
|
1010
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
1011
|
+
* import { Effect } from "effect"
|
|
1012
|
+
*
|
|
1013
|
+
* const chain = These.flatMapLeftEffect((left: number) =>
|
|
1014
|
+
* Effect.succeed(These.LeftOnly({ left: left * 10 }))
|
|
1015
|
+
* )
|
|
1016
|
+
*
|
|
1017
|
+
* assert.deepStrictEqual(
|
|
1018
|
+
* Effect.runSync(chain(These.LeftOnly({ left: 2 }))),
|
|
1019
|
+
* These.LeftOnly({ left: 20 })
|
|
1020
|
+
* )
|
|
1021
|
+
* assert.deepStrictEqual(
|
|
1022
|
+
* Effect.runSync(chain(These.RightOnly({ right: "a" }))),
|
|
1023
|
+
* These.RightOnly({ right: "a" })
|
|
1024
|
+
* )
|
|
1025
|
+
* ```
|
|
1026
|
+
*
|
|
1027
|
+
* @category sequencing
|
|
1028
|
+
* @since 0.0.0
|
|
1029
|
+
*/
|
|
1030
|
+
export const flatMapLeftEffect = <L1, L2, R2, EL, RL>(
|
|
1031
|
+
mapLeft: (left: L1) => Effect.Effect<These<L2, R2>, EL, RL>,
|
|
1032
|
+
): (<R1>(these: These<L1, R1>) => Effect.Effect<These<L2, R1 | R2>, EL, RL>) =>
|
|
1033
|
+
match({
|
|
1034
|
+
LeftOnly: ({ left }) => mapLeft(left),
|
|
1035
|
+
RightOnly: ({ right }) => Effect.succeed(RightOnly({ right })),
|
|
1036
|
+
LeftAndRight: ({ left }) => mapLeft(left),
|
|
1037
|
+
});
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Transforms the `right` of a `These`, leaving any `left` untouched.
|
|
1041
|
+
*
|
|
1042
|
+
* The mirror of `mapLeft`: `RightOnly` and `LeftAndRight` have their `right`
|
|
1043
|
+
* mapped, while `LeftOnly` passes through unchanged.
|
|
1044
|
+
*
|
|
1045
|
+
* @example
|
|
1046
|
+
* ```ts
|
|
1047
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
1048
|
+
*
|
|
1049
|
+
* const upper = These.mapRight((right: string) => right.toUpperCase())
|
|
1050
|
+
*
|
|
1051
|
+
* assert.deepStrictEqual(
|
|
1052
|
+
* upper(These.LeftAndRight({ left: 1, right: "a" })),
|
|
1053
|
+
* These.LeftAndRight({ left: 1, right: "A" })
|
|
1054
|
+
* )
|
|
1055
|
+
* assert.deepStrictEqual(
|
|
1056
|
+
* upper(These.LeftOnly({ left: 1 })),
|
|
1057
|
+
* These.LeftOnly({ left: 1 })
|
|
1058
|
+
* )
|
|
1059
|
+
* ```
|
|
1060
|
+
*
|
|
1061
|
+
* @category mapping
|
|
1062
|
+
* @since 0.0.0
|
|
1063
|
+
*/
|
|
1064
|
+
export const mapRight = <R1, R2>(
|
|
1065
|
+
mapRight: (right: R1) => R2,
|
|
1066
|
+
): (<L>(these: These<L, R1>) => These<L, R2>) =>
|
|
1067
|
+
mapBoth({ mapLeft: identity, mapRight });
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* Chains the `right` of a `These` into a new `These`, flattening the result.
|
|
1071
|
+
*
|
|
1072
|
+
* The mirror of `flatMapLeft`: whenever a `right` is present (`RightOnly` or
|
|
1073
|
+
* `LeftAndRight`) it is passed to `mapRight`, whose returned `These` replaces the
|
|
1074
|
+
* original; `LeftOnly` passes through unchanged.
|
|
1075
|
+
*
|
|
1076
|
+
* @example
|
|
1077
|
+
* ```ts
|
|
1078
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
1079
|
+
*
|
|
1080
|
+
* const chain = These.flatMapRight((right: string) =>
|
|
1081
|
+
* These.RightOnly({ right: right.toUpperCase() })
|
|
1082
|
+
* )
|
|
1083
|
+
*
|
|
1084
|
+
* assert.deepStrictEqual(
|
|
1085
|
+
* chain(These.RightOnly({ right: "a" })),
|
|
1086
|
+
* These.RightOnly({ right: "A" })
|
|
1087
|
+
* )
|
|
1088
|
+
* assert.deepStrictEqual(
|
|
1089
|
+
* chain(These.LeftOnly({ left: 1 })),
|
|
1090
|
+
* These.LeftOnly({ left: 1 })
|
|
1091
|
+
* )
|
|
1092
|
+
* ```
|
|
1093
|
+
*
|
|
1094
|
+
* @category sequencing
|
|
1095
|
+
* @since 0.0.0
|
|
1096
|
+
*/
|
|
1097
|
+
export const flatMapRight = <L2, R1, R2>(
|
|
1098
|
+
mapRight: (right: R1) => These<L2, R2>,
|
|
1099
|
+
): (<L1>(these: These<L1, R1>) => These<L1 | L2, R2>) =>
|
|
1100
|
+
match({
|
|
1101
|
+
LeftOnly: ({ left }) => LeftOnly({ left }),
|
|
1102
|
+
RightOnly: ({ right }) => mapRight(right),
|
|
1103
|
+
LeftAndRight: ({ right }) => mapRight(right),
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* Effectful `mapRight`: transforms the `right` of a `These` through an `Effect`,
|
|
1108
|
+
* leaving any `left` untouched.
|
|
1109
|
+
*
|
|
1110
|
+
* The mirror of `mapLeftEffect`: the `right` (when present) is mapped effectfully
|
|
1111
|
+
* and the `left` is carried through unchanged via `Effect.succeed`.
|
|
1112
|
+
*
|
|
1113
|
+
* @example
|
|
1114
|
+
* ```ts
|
|
1115
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
1116
|
+
* import { Effect } from "effect"
|
|
1117
|
+
*
|
|
1118
|
+
* const upper = These.mapRightEffect((right: string) =>
|
|
1119
|
+
* Effect.succeed(right.toUpperCase())
|
|
1120
|
+
* )
|
|
1121
|
+
*
|
|
1122
|
+
* assert.deepStrictEqual(
|
|
1123
|
+
* Effect.runSync(upper(These.LeftAndRight({ left: 1, right: "a" }))),
|
|
1124
|
+
* These.LeftAndRight({ left: 1, right: "A" })
|
|
1125
|
+
* )
|
|
1126
|
+
* ```
|
|
1127
|
+
*
|
|
1128
|
+
* @category sequencing
|
|
1129
|
+
* @since 0.0.0
|
|
1130
|
+
*/
|
|
1131
|
+
export const mapRightEffect = <R1, R2, ER, RR>(
|
|
1132
|
+
mapRight: (right: R1) => Effect.Effect<R2, ER, RR>,
|
|
1133
|
+
): (<L>(these: These<L, R1>) => Effect.Effect<These<L, R2>, ER, RR>) =>
|
|
1134
|
+
mapBothEffect({ mapLeft: Effect.succeed, mapRight });
|
|
1135
|
+
|
|
1136
|
+
/**
|
|
1137
|
+
* Effectful `flatMapRight`: chains the `right` of a `These` into an `Effect` that
|
|
1138
|
+
* yields a new `These`, flattening the result.
|
|
1139
|
+
*
|
|
1140
|
+
* The mirror of `flatMapLeftEffect`: when a `right` is present it is passed to
|
|
1141
|
+
* `mapRight`, whose effectful `These` replaces the original; `LeftOnly` is lifted
|
|
1142
|
+
* unchanged via `Effect.succeed`.
|
|
1143
|
+
*
|
|
1144
|
+
* @example
|
|
1145
|
+
* ```ts
|
|
1146
|
+
* import { These } from "@nunofyobiz/effect-extras"
|
|
1147
|
+
* import { Effect } from "effect"
|
|
1148
|
+
*
|
|
1149
|
+
* const chain = These.flatMapRightEffect((right: string) =>
|
|
1150
|
+
* Effect.succeed(These.RightOnly({ right: right.toUpperCase() }))
|
|
1151
|
+
* )
|
|
1152
|
+
*
|
|
1153
|
+
* assert.deepStrictEqual(
|
|
1154
|
+
* Effect.runSync(chain(These.RightOnly({ right: "a" }))),
|
|
1155
|
+
* These.RightOnly({ right: "A" })
|
|
1156
|
+
* )
|
|
1157
|
+
* assert.deepStrictEqual(
|
|
1158
|
+
* Effect.runSync(chain(These.LeftOnly({ left: 1 }))),
|
|
1159
|
+
* These.LeftOnly({ left: 1 })
|
|
1160
|
+
* )
|
|
1161
|
+
* ```
|
|
1162
|
+
*
|
|
1163
|
+
* @category sequencing
|
|
1164
|
+
* @since 0.0.0
|
|
1165
|
+
*/
|
|
1166
|
+
export const flatMapRightEffect = <L2, R1, R2, ER, RR>(
|
|
1167
|
+
mapRight: (right: R1) => Effect.Effect<These<L2, R2>, ER, RR>,
|
|
1168
|
+
): (<L1>(these: These<L1, R1>) => Effect.Effect<These<L1 | L2, R2>, ER, RR>) =>
|
|
1169
|
+
match({
|
|
1170
|
+
LeftOnly: ({ left }) => Effect.succeed(LeftOnly({ left })),
|
|
1171
|
+
RightOnly: ({ right }) => mapRight(right),
|
|
1172
|
+
LeftAndRight: ({ right }) => mapRight(right),
|
|
1173
|
+
});
|