1/**
2 * Works with fixed-length arrays, also called tuples.
3 *
4 * The runtime helpers in this module create new tuples instead of mutating
5 * their inputs, and the types preserve element positions where possible. The
6 * helpers cover tuple construction, indexed access, selecting or removing
7 * positions, appending values, transforming elements, renaming indices, mapping
8 * typed positions, and deriving comparison or combination helpers for tuple
9 * shapes.
10 *
11 * @since 2.0.0
12 */
13import * as Combiner from "./Combiner.ts"
14import * as Equivalence from "./Equivalence.ts"
15import { dual } from "./Function.ts"
16import * as order from "./Order.ts"
17import * as Reducer from "./Reducer.ts"
18import type { Apply, Lambda } from "./Struct.ts"
19
20/**
21 * Creates a tuple from the provided arguments.
22 *
23 * **When to use**
24 *
25 * Use when you need a properly typed tuple without writing `[a, b, c] as const`
26 * or another manual cast.
27 *
28 * **Details**
29 *
30 * The returned value has the exact tuple type, with each element's literal type
31 * preserved.
32 *
33 * **Example** (Creating a tuple)
34 *
35 * ```ts import.meta.vitest
36 * import { Tuple } from "effect"
37 *
38 * Tuple.make(10, 20, "red") // => [10, 20, "red"]
39 * ```
40 *
41 * @see {@link get} – access a single element by index
42 * @see {@link appendElement} – append an element to a tuple
43 * @category constructors
44 * @since 2.0.0
45 */
46export const make = <const Elements extends ReadonlyArray<unknown>>(...elements: [...Elements]): [...Elements] =>
47 elements
48
49type Indices<T extends ReadonlyArray<unknown>> = Exclude<Partial<T>["length"], T["length"]>
50
51/**
52 * Retrieves the element at the specified index from a tuple.
53 *
54 * **When to use**
55 *
56 * Use when a single tuple element should be extracted in a pipeline.
57 *
58 * **Details**
59 *
60 * The index is constrained to valid tuple positions at the type level.
61 *
62 * **Example** (Extracting an element by index)
63 *
64 * ```ts import.meta.vitest
65 * import { pipe, Tuple } from "effect"
66 *
67 * pipe(Tuple.make(1, true, "hello"), Tuple.get(2)) // => "hello"
68 * ```
69 *
70 * @see {@link make} – create a tuple
71 * @see {@link pick} – extract multiple elements into a new tuple
72 * @category getters
73 * @since 4.0.0
74 */
75export const get: {
76 <const T extends ReadonlyArray<unknown>, I extends Indices<T> & keyof T>(index: I): (self: T) => T[I]
77 <const T extends ReadonlyArray<unknown>, I extends Indices<T> & keyof T>(self: T, index: I): T[I]
78} = dual(2, <T extends ReadonlyArray<unknown>, I extends keyof T>(self: T, index: I): T[I] => self[index])
79
80type _BuildTuple<
81 T extends ReadonlyArray<unknown>,
82 K,
83 Acc extends ReadonlyArray<unknown> = [],
84 I extends ReadonlyArray<unknown> = [] // current index counter
85> = I["length"] extends T["length"] ? Acc
86 : _BuildTuple<
87 T,
88 K,
89 // If current index is in K, keep the element; otherwise skip it
90 I["length"] extends K ? [...Acc, T[I["length"]]] : Acc,
91 [...I, unknown]
92 >
93
94type PickTuple<T extends ReadonlyArray<unknown>, I extends ReadonlyArray<Indices<T>>> = {
95 -readonly [K in keyof I]: T[I[K] & keyof T]
96}
97
98/**
99 * Creates a new tuple containing only the elements at the specified indices.
100 *
101 * **When to use**
102 *
103 * Use to select a subset of elements from a tuple by position.
104 *
105 * **Details**
106 *
107 * The result order matches the order of the provided indices.
108 *
109 * **Example** (Selecting elements by index)
110 *
111 * ```ts import.meta.vitest
112 * import { Tuple } from "effect"
113 *
114 * Tuple.pick(["a", "b", "c", "d"], [0, 2, 3]) // => ["a", "c", "d"]
115 * ```
116 *
117 * @see {@link omit} – the inverse (exclude indices instead)
118 * @see {@link get} – extract a single element
119 * @category filtering
120 * @since 4.0.0
121 */
122export const pick: {
123 <const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>>(
124 indices: I
125 ): (self: T) => PickTuple<T, I>
126 <const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>>(
127 self: T,
128 indices: I
129 ): PickTuple<T, I>
130} = dual(
131 2,
132 <const T extends ReadonlyArray<unknown>>(
133 self: T,
134 indices: ReadonlyArray<number>
135 ) => {
136 return indices.map((i) => self[i])
137 }
138)
139
140type OmitTuple<T extends ReadonlyArray<unknown>, K> = _BuildTuple<T, Exclude<Indices<T>, K>>
141
142/**
143 * Creates a new tuple with the elements at the specified indices removed.
144 *
145 * **When to use**
146 *
147 * Use to drop elements from a tuple by position.
148 *
149 * **Details**
150 *
151 * Elements not at the specified indices are kept in their original order.
152 *
153 * **Example** (Removing elements by index)
154 *
155 * ```ts import.meta.vitest
156 * import { Tuple } from "effect"
157 *
158 * Tuple.omit(["a", "b", "c", "d"], [1, 3]) // => ["a", "c"]
159 * ```
160 *
161 * @see {@link pick} – the inverse (keep only specified indices)
162 * @category filtering
163 * @since 4.0.0
164 */
165export const omit: {
166 <const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>>(
167 indices: I
168 ): (self: T) => OmitTuple<T, I[number]>
169 <const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>>(
170 self: T,
171 indices: I
172 ): OmitTuple<T, I[number]>
173} = dual(
174 2,
175 <const T extends ReadonlyArray<unknown>>(
176 self: T,
177 indices: ReadonlyArray<number>
178 ) => {
179 const toDrop = new Set<number>(indices)
180 return self.filter((_, i) => !toDrop.has(i))
181 }
182)
183
184/**
185 * Appends a single element to the end of a tuple.
186 *
187 * **When to use**
188 *
189 * Use when you need the appended value to remain part of the tuple's type-level
190 * shape and preserve literal element positions.
191 *
192 * **Details**
193 *
194 * The result type is `[...T, E]`, preserving all existing element types.
195 *
196 * **Example** (Appending an element)
197 *
198 * ```ts import.meta.vitest
199 * import { pipe, Tuple } from "effect"
200 *
201 * pipe(Tuple.make(1, 2), Tuple.appendElement("end")) // => [1, 2, "end"]
202 * ```
203 *
204 * @see {@link appendElements} – append multiple elements (another tuple)
205 * @category combining
206 * @since 2.0.0
207 */
208export const appendElement: {
209 <const E>(element: E): <const T extends ReadonlyArray<unknown>>(self: T) => [...T, E]
210 <const T extends ReadonlyArray<unknown>, const E>(self: T, element: E): [...T, E]
211} = dual(2, <T extends ReadonlyArray<unknown>, E>(self: T, element: E): [...T, E] => [...self, element])
212
213/**
214 * Concatenates two tuples into a single tuple.
215 *
216 * **When to use**
217 *
218 * Use to append all elements from one tuple to another tuple.
219 *
220 * **Details**
221 *
222 * The result type is `[...T1, ...T2]`, preserving all element types from both
223 * tuples. Neither input tuple is mutated; a fresh tuple is returned.
224 *
225 * **Example** (Concatenating tuples)
226 *
227 * ```ts import.meta.vitest
228 * import { pipe, Tuple } from "effect"
229 *
230 * pipe(Tuple.make(1, 2), Tuple.appendElements(["a", "b"] as const)) // => [1, 2, "a", "b"]
231 * ```
232 *
233 * @see {@link appendElement} – append a single element
234 * @category combining
235 * @since 4.0.0
236 */
237export const appendElements: {
238 <const T2 extends ReadonlyArray<unknown>>(
239 that: T2
240 ): <const T1 extends ReadonlyArray<unknown>>(self: T1) => [...T1, ...T2]
241 <const T1 extends ReadonlyArray<unknown>, const T2 extends ReadonlyArray<unknown>>(self: T1, that: T2): [...T1, ...T2]
242} = dual(
243 2,
244 <T1 extends ReadonlyArray<unknown>, T2 extends ReadonlyArray<unknown>>(
245 self: T1,
246 that: T2
247 ): [...T1, ...T2] => [...self, ...that]
248)
249
250type Evolver<T> = { readonly [I in keyof T]?: ((a: T[I]) => unknown) | undefined }
251
252type EvolveElement<A, F> = F extends (...a: any) => infer R ? R : A
253
254type Evolved<T, E> = { [I in keyof T]: I extends keyof E ? EvolveElement<T[I], E[I]> : T[I] }
255
256/**
257 * Transforms elements of a tuple by providing an array of transform functions.
258 * Each function applies to the element at the same position. Positions beyond
259 * the array's length are copied unchanged.
260 *
261 * **When to use**
262 *
263 * Use when you want to update the first N elements while keeping the rest.
264 *
265 * **Details**
266 *
267 * Each transform function receives the current value and can return a different
268 * type.
269 *
270 * **Example** (Transforming selected elements)
271 *
272 * ```ts import.meta.vitest
273 * import { pipe, Tuple } from "effect"
274 *
275 * pipe(
276 * Tuple.make("hello", 42, true),
277 * Tuple.evolve([
278 * (s) => s.toUpperCase(),
279 * (n) => n * 2
280 * ])
281 * ) // => ["HELLO", 84, true]
282 * ```
283 *
284 * @see {@link map} – apply the same transformation to all elements
285 * @see {@link renameIndices} – swap element positions
286 * @category mapping
287 * @since 4.0.0
288 */
289export const evolve: {
290 <const T extends ReadonlyArray<unknown>, const E extends Evolver<T>>(evolver: E): (self: T) => Evolved<T, E>
291 <const T extends ReadonlyArray<unknown>, const E extends Evolver<T>>(self: T, evolver: E): Evolved<T, E>
292} = dual(
293 2,
294 <const T extends ReadonlyArray<unknown>, const E extends Evolver<T>>(self: T, evolver: E) => {
295 return self.map((e, i) => (evolver[i] !== undefined ? evolver[i](e) : e))
296 }
297)
298
299/**
300 * Renames tuple indices by providing an array of stringified source
301 * indices. Each position in the array specifies which index to read from
302 * (e.g., `["2", "1", "0"]` reverses a 3-element tuple).
303 *
304 * **When to use**
305 *
306 * Use to reorder tuple elements while preserving index-specific types.
307 *
308 * **Details**
309 *
310 * The mapping returns a tuple in the requested index order.
311 *
312 * **Gotchas**
313 *
314 * The mapping uses stringified source indices, not arbitrary names.
315 *
316 * **Example** (Swapping elements)
317 *
318 * ```ts import.meta.vitest
319 * import { pipe, Tuple } from "effect"
320 *
321 * pipe(
322 * Tuple.make("a", "b", "c"),
323 * Tuple.renameIndices(["2", "1", "0"])
324 * ) // => ["c", "b", "a"]
325 * ```
326 *
327 * @see {@link evolve} – transform element values instead of positions
328 * @category transforming
329 * @since 4.0.0
330 */
331export const renameIndices: {
332 <const T extends ReadonlyArray<unknown>, const M extends { readonly [I in keyof T]?: `${keyof T & string}` }>(
333 mapping: M
334 ): (self: T) => { [I in keyof T]: I extends keyof M ? M[I] extends keyof T ? T[M[I]] : T[I] : T[I] }
335 <const T extends ReadonlyArray<unknown>, const M extends { readonly [I in keyof T]?: `${keyof T & string}` }>(
336 self: T,
337 mapping: M
338 ): { [I in keyof T]: I extends keyof M ? M[I] extends keyof T ? T[M[I]] : T[I] : T[I] }
339} = dual(
340 2,
341 <const T extends ReadonlyArray<unknown>, const M extends { readonly [I in keyof T]?: `${keyof T & string}` }>(
342 self: T,
343 mapping: M
344 ) => {
345 return self.map((e, i) => mapping[i] !== undefined ? self[mapping[i]] : e)
346 }
347)
348
349/**
350 * Applies a `Struct.Lambda` transformation to every element in a tuple.
351 *
352 * **When to use**
353 *
354 * Use when you want to apply the same transformation to every tuple element.
355 *
356 * **Details**
357 *
358 * The lambda lets the compiler track the output type for each element.
359 *
360 * **Gotchas**
361 *
362 * The lambda must be created with `Struct.lambda`; a plain function will not
363 * type-check.
364 *
365 * **Example** (Wrapping every element in an array)
366 *
367 * ```ts import.meta.vitest
368 * import { pipe, Struct, Tuple } from "effect"
369 *
370 * interface AsArray extends Struct.Lambda {
371 * <A>(self: A): Array<A>
372 * readonly "~lambda.out": Array<this["~lambda.in"]>
373 * }
374 *
375 * const asArray = Struct.lambda<AsArray>((a) => [a])
376 * pipe(Tuple.make(1, "hello", true), Tuple.map(asArray)) // => [[1], ["hello"], [true]]
377 * ```
378 *
379 * @see {@link mapPick} – apply a lambda only to selected indices
380 * @see {@link mapOmit} – apply a lambda to all indices except selected ones
381 * @see {@link evolve} – apply different functions to different indices
382 * @category mapping
383 * @since 3.9.0
384 */
385export const map: {
386 <L extends Lambda>(
387 lambda: L
388 ): <const T extends ReadonlyArray<unknown>>(
389 self: T
390 ) => { [K in keyof T]: Apply<L, T[K]> }
391 <const T extends ReadonlyArray<unknown>, L extends Lambda>(
392 self: T,
393 lambda: L
394 ): { [K in keyof T]: Apply<L, T[K]> }
395} = dual(
396 2,
397 <const T extends ReadonlyArray<unknown>, L extends Function>(self: T, lambda: L) => {
398 return self.map((e) => lambda(e))
399 }
400)
401
402/**
403 * Applies a `Struct.Lambda` transformation only to the elements at the
404 * specified indices; all other elements are copied unchanged.
405 *
406 * **When to use**
407 *
408 * Use when you want to apply the same transformation to a subset of
409 * positions.
410 *
411 * **Example** (Wrapping only selected elements in arrays)
412 *
413 * ```ts import.meta.vitest
414 * import { pipe, Struct, Tuple } from "effect"
415 *
416 * interface AsArray extends Struct.Lambda {
417 * <A>(self: A): Array<A>
418 * readonly "~lambda.out": Array<this["~lambda.in"]>
419 * }
420 *
421 * const asArray = Struct.lambda<AsArray>((a) => [a])
422 * pipe(
423 * Tuple.make(1, "hello", true),
424 * Tuple.mapPick([0, 2], asArray)
425 * ) // => [[1], "hello", [true]]
426 * ```
427 *
428 * @see {@link map} – apply a lambda to all elements
429 * @see {@link mapOmit} – apply a lambda to all elements except selected ones
430 * @category mapping
431 * @since 4.0.0
432 */
433export const mapPick: {
434 <const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>, L extends Lambda>(
435 indices: I,
436 lambda: L
437 ): (
438 self: T
439 ) => { [K in keyof T]: K extends `${I[number]}` ? Apply<L, T[K]> : T[K] }
440 <const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>, L extends Lambda>(
441 self: T,
442 indices: I,
443 lambda: L
444 ): { [K in keyof T]: K extends `${I[number]}` ? Apply<L, T[K]> : T[K] }
445} = dual(
446 3,
447 <const T extends ReadonlyArray<unknown>, L extends Function>(
448 self: T,
449 indices: ReadonlyArray<number>,
450 lambda: L
451 ) => {
452 const toPick = new Set<number>(indices)
453 return self.map((e, i) => (toPick.has(i) ? lambda(e) : e))
454 }
455)
456
457/**
458 * Applies a `Struct.Lambda` transformation to all elements except those at the
459 * specified indices; the excluded elements are copied unchanged.
460 *
461 * **When to use**
462 *
463 * Use when most elements should be transformed but a few should be
464 * preserved.
465 *
466 * **Example** (Wrapping all elements except one in arrays)
467 *
468 * ```ts import.meta.vitest
469 * import { pipe, Struct, Tuple } from "effect"
470 *
471 * interface AsArray extends Struct.Lambda {
472 * <A>(self: A): Array<A>
473 * readonly "~lambda.out": Array<this["~lambda.in"]>
474 * }
475 *
476 * const asArray = Struct.lambda<AsArray>((a) => [a])
477 * pipe(
478 * Tuple.make(1, "hello", true),
479 * Tuple.mapOmit([1], asArray)
480 * ) // => [[1], "hello", [true]]
481 * ```
482 *
483 * @see {@link map} – apply a lambda to all elements
484 * @see {@link mapPick} – apply a lambda only to selected indices
485 * @category mapping
486 * @since 4.0.0
487 */
488export const mapOmit: {
489 <const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>, L extends Lambda>(
490 indices: I,
491 lambda: L
492 ): (
493 self: T
494 ) => { [K in keyof T]: K extends `${I[number]}` ? T[K] : Apply<L, T[K]> }
495 <const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>, L extends Lambda>(
496 self: T,
497 indices: I,
498 lambda: L
499 ): { [K in keyof T]: K extends `${I[number]}` ? T[K] : Apply<L, T[K]> }
500} = dual(
501 3,
502 <const T extends ReadonlyArray<unknown>, L extends Function>(
503 self: T,
504 indices: ReadonlyArray<number>,
505 lambda: L
506 ) => {
507 const toOmit = new Set<number>(indices)
508 return self.map((e, i) => (toOmit.has(i) ? e : lambda(e)))
509 }
510)
511
512/**
513 * Creates an `Equivalence` for tuples by comparing corresponding elements
514 * using the provided per-position `Equivalence`s. Two tuples are equivalent
515 * when all their corresponding elements are equivalent.
516 *
517 * **When to use**
518 *
519 * Use when you need an `Equivalence` to compare tuples element-by-element.
520 *
521 * **Details**
522 *
523 * This is an alias of `Equivalence.Tuple`.
524 *
525 * **Example** (Comparing tuples for equivalence)
526 *
527 * ```ts import.meta.vitest
528 * import { Equivalence, Tuple } from "effect"
529 *
530 * const eq = Tuple.makeEquivalence([
531 * Equivalence.strictEqual<string>(),
532 * Equivalence.strictEqual<number>()
533 * ])
534 *
535 * eq(["Alice", 30], ["Alice", 30]) // => true
536 * eq(["Alice", 30], ["Bob", 30]) // => false
537 * ```
538 *
539 * @see {@link makeOrder} – create an `Order` for tuples
540 * @category instances
541 * @since 4.0.0
542 */
543export const makeEquivalence = Equivalence.Tuple
544
545/**
546 * Creates an `Order` for tuples by comparing corresponding elements using the
547 * provided per-position `Order`s. Elements are compared left-to-right; the
548 * first non-zero comparison determines the result.
549 *
550 * **When to use**
551 *
552 * Use when you need to sort fixed-position arrays lexicographically, with each
553 * position using its own ordering rule.
554 *
555 * **Details**
556 *
557 * This is an alias of `Order.Tuple`.
558 *
559 * **Example** (Ordering tuples)
560 *
561 * ```ts import.meta.vitest
562 * import { Number, String, Tuple } from "effect"
563 *
564 * const ord = Tuple.makeOrder([String.Order, Number.Order])
565 *
566 * ord(["Alice", 30], ["Bob", 25]) // => -1
567 * ord(["Alice", 30], ["Alice", 30]) // => 0
568 * ```
569 *
570 * @see {@link makeEquivalence} – create an `Equivalence` for tuples
571 * @category ordering
572 * @since 4.0.0
573 */
574export const makeOrder = order.Tuple
575
576export {
577 /**
578 * Checks whether an array has exactly `N` elements, narrowing the type to a
579 * fixed-length tuple.
580 *
581 * **When to use**
582 *
583 * Use to guard that an array has exactly the tuple length expected at
584 * runtime.
585 *
586 * **Details**
587 *
588 * This is a re-export of `Predicate.isTupleOf`. It narrows the type to
589 * `TupleOf<N, T>` in the truthy branch.
590 *
591 * **Gotchas**
592 *
593 * This only checks `.length`; it does not validate element types.
594 *
595 * **Example** (Checking exact length)
596 *
597 * ```ts import.meta.vitest
598 * import { Tuple } from "effect"
599 *
600 * const arr: Array<number> = [1, 2, 3]
601 * if (Tuple.isTupleOf(arr, 3)) {
602 * arr // => [1, 2, 3]
603 * }
604 * ```
605 *
606 * @see `isTupleOfAtLeast` – check for a minimum length
607 * @category guards
608 * @since 3.3.0
609 */
610 isTupleOf,
611 /**
612 * Checks whether an array has at least `N` elements, narrowing the type to a
613 * tuple with a minimum length.
614 *
615 * **When to use**
616 *
617 * Use to guard that an array has at least the tuple length expected at
618 * runtime.
619 *
620 * **Details**
621 *
622 * This is a re-export of `Predicate.isTupleOfAtLeast`. It narrows the type to
623 * `TupleOfAtLeast<N, T>` in the truthy branch.
624 *
625 * **Gotchas**
626 *
627 * This only checks `.length`; it does not validate element types.
628 *
629 * **Example** (Checking minimum length)
630 *
631 * ```ts import.meta.vitest
632 * import { Tuple } from "effect"
633 *
634 * const arr: Array<number> = [1, 2, 3, 4]
635 * if (Tuple.isTupleOfAtLeast(arr, 3)) {
636 * arr // => [1, 2, 3, 4]
637 * }
638 * ```
639 *
640 * @see `isTupleOf` – check for an exact length
641 * @category guards
642 * @since 3.3.0
643 */
644 isTupleOfAtLeast
645} from "./Predicate.ts"
646
647/**
648 * Creates a `Combiner` for a tuple shape by providing a `Combiner` for each
649 * position. When two tuples are combined, each element is merged using its
650 * corresponding combiner.
651 *
652 * **When to use**
653 *
654 * Use when you need to merge two same-shape tuples by combining each position
655 * independently, such as summing counters or concatenating strings.
656 *
657 * **Example** (Combining tuple elements)
658 *
659 * ```ts import.meta.vitest
660 * import { Number, String, Tuple } from "effect"
661 *
662 * const C = Tuple.makeCombiner<readonly [number, string]>([
663 * Number.ReducerSum,
664 * String.ReducerConcat
665 * ])
666 *
667 * C.combine([1, "hello"], [2, " world"]) // => [3, "hello world"]
668 * ```
669 *
670 * @see {@link makeReducer} – like `makeCombiner` but with an initial value
671 * @category combining
672 * @since 4.0.0
673 */
674export function makeCombiner<A extends ReadonlyArray<unknown>>(
675 combiners: { readonly [K in keyof A]: Combiner.Combiner<A[K]> }
676): Combiner.Combiner<A> {
677 return Combiner.make((self, that) => {
678 const out = []
679 for (let i = 0; i < self.length; i++) {
680 out.push(combiners[i].combine(self[i], that[i]))
681 }
682 return out as any
683 })
684}
685
686/**
687 * Creates a `Reducer` for a tuple shape by providing a `Reducer` for each
688 * position. The initial value is derived from each position's
689 * `Reducer.initialValue`. When reducing a collection of tuples, each element
690 * is combined independently.
691 *
692 * **When to use**
693 *
694 * Use when you need to fold same-shape tuples by accumulating each position
695 * independently into one summary tuple.
696 *
697 * **Example** (Reducing a collection of tuples)
698 *
699 * ```ts import.meta.vitest
700 * import { Number, String, Tuple } from "effect"
701 *
702 * const R = Tuple.makeReducer<readonly [number, string]>([
703 * Number.ReducerSum,
704 * String.ReducerConcat
705 * ])
706 *
707 * R.combineAll([
708 * [1, "a"],
709 * [2, "b"],
710 * [3, "c"]
711 * ]) // => [6, "abc"]
712 * ```
713 *
714 * @see {@link makeCombiner} – like `makeReducer` but without an initial value
715 * @category folding
716 * @since 4.0.0
717 */
718export function makeReducer<A extends ReadonlyArray<unknown>>(
719 reducers: { readonly [K in keyof A]: Reducer.Reducer<A[K]> }
720): Reducer.Reducer<A> {
721 const combine = makeCombiner(reducers).combine
722 const initialValue = []
723 for (let i = 0; i < reducers.length; i++) {
724 initialValue.push(reducers[i].initialValue)
725 }
726 return Reducer.make(combine, initialValue as unknown as A)
727}