738 lines
21 KiB
TypeScript
738 lines
21 KiB
TypeScript
/**
|
||
* Works with fixed-length arrays, also called tuples.
|
||
*
|
||
* The runtime helpers in this module create new tuples instead of mutating
|
||
* their inputs, and the types preserve element positions where possible. The
|
||
* helpers cover tuple construction, indexed access, selecting or removing
|
||
* positions, appending values, transforming elements, renaming indices, mapping
|
||
* typed positions, and deriving comparison or combination helpers for tuple
|
||
* shapes.
|
||
*
|
||
* @since 2.0.0
|
||
*/
|
||
import * as Combiner from "./Combiner.ts"
|
||
import * as Equivalence from "./Equivalence.ts"
|
||
import { dual } from "./Function.ts"
|
||
import * as order from "./Order.ts"
|
||
import * as Reducer from "./Reducer.ts"
|
||
import type { Apply, Lambda } from "./Struct.ts"
|
||
|
||
/**
|
||
* Creates a tuple from the provided arguments.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when you need a properly typed tuple without writing `[a, b, c] as const`
|
||
* or another manual cast.
|
||
*
|
||
* **Details**
|
||
*
|
||
* The returned value has the exact tuple type, with each element's literal type
|
||
* preserved.
|
||
*
|
||
* **Example** (Creating a tuple)
|
||
*
|
||
* ```ts
|
||
* import { Tuple } from "effect"
|
||
*
|
||
* const point = Tuple.make(10, 20, "red")
|
||
* console.log(point) // [10, 20, "red"]
|
||
* ```
|
||
*
|
||
* @see {@link get} – access a single element by index
|
||
* @see {@link appendElement} – append an element to a tuple
|
||
* @category constructors
|
||
* @since 2.0.0
|
||
*/
|
||
export const make = <Elements extends ReadonlyArray<unknown>>(...elements: Elements): Elements => elements
|
||
|
||
type Indices<T extends ReadonlyArray<unknown>> = Exclude<Partial<T>["length"], T["length"]>
|
||
|
||
/**
|
||
* Retrieves the element at the specified index from a tuple.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when a single tuple element should be extracted in a pipeline.
|
||
*
|
||
* **Details**
|
||
*
|
||
* The index is constrained to valid tuple positions at the type level.
|
||
*
|
||
* **Example** (Extracting an element by index)
|
||
*
|
||
* ```ts
|
||
* import { pipe, Tuple } from "effect"
|
||
*
|
||
* const last = pipe(Tuple.make(1, true, "hello"), Tuple.get(2))
|
||
* console.log(last) // "hello"
|
||
* ```
|
||
*
|
||
* @see {@link make} – create a tuple
|
||
* @see {@link pick} – extract multiple elements into a new tuple
|
||
* @category getters
|
||
* @since 4.0.0
|
||
*/
|
||
export const get: {
|
||
<const T extends ReadonlyArray<unknown>, I extends Indices<T> & keyof T>(index: I): (self: T) => T[I]
|
||
<const T extends ReadonlyArray<unknown>, I extends Indices<T> & keyof T>(self: T, index: I): T[I]
|
||
} = dual(2, <T extends ReadonlyArray<unknown>, I extends keyof T>(self: T, index: I): T[I] => self[index])
|
||
|
||
type _BuildTuple<
|
||
T extends ReadonlyArray<unknown>,
|
||
K,
|
||
Acc extends ReadonlyArray<unknown> = [],
|
||
I extends ReadonlyArray<unknown> = [] // current index counter
|
||
> = I["length"] extends T["length"] ? Acc
|
||
: _BuildTuple<
|
||
T,
|
||
K,
|
||
// If current index is in K, keep the element; otherwise skip it
|
||
I["length"] extends K ? [...Acc, T[I["length"]]] : Acc,
|
||
[...I, unknown]
|
||
>
|
||
|
||
type PickTuple<T extends ReadonlyArray<unknown>, K> = _BuildTuple<T, K>
|
||
|
||
/**
|
||
* Creates a new tuple containing only the elements at the specified indices.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use to select a subset of elements from a tuple by position.
|
||
*
|
||
* **Details**
|
||
*
|
||
* The result order matches the order of the provided indices.
|
||
*
|
||
* **Example** (Selecting elements by index)
|
||
*
|
||
* ```ts
|
||
* import { Tuple } from "effect"
|
||
*
|
||
* const result = Tuple.pick(["a", "b", "c", "d"], [0, 2, 3])
|
||
* console.log(result) // ["a", "c", "d"]
|
||
* ```
|
||
*
|
||
* @see {@link omit} – the inverse (exclude indices instead)
|
||
* @see {@link get} – extract a single element
|
||
* @category filtering
|
||
* @since 4.0.0
|
||
*/
|
||
export const pick: {
|
||
<const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>>(
|
||
indices: I
|
||
): (self: T) => PickTuple<T, I[number]>
|
||
<const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>>(
|
||
self: T,
|
||
indices: I
|
||
): PickTuple<T, I[number]>
|
||
} = dual(
|
||
2,
|
||
<const T extends ReadonlyArray<unknown>>(
|
||
self: T,
|
||
indices: ReadonlyArray<number>
|
||
) => {
|
||
return indices.map((i) => self[i])
|
||
}
|
||
)
|
||
|
||
type OmitTuple<T extends ReadonlyArray<unknown>, K> = _BuildTuple<T, Exclude<Indices<T>, K>>
|
||
|
||
/**
|
||
* Creates a new tuple with the elements at the specified indices removed.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use to drop elements from a tuple by position.
|
||
*
|
||
* **Details**
|
||
*
|
||
* Elements not at the specified indices are kept in their original order.
|
||
*
|
||
* **Example** (Removing elements by index)
|
||
*
|
||
* ```ts
|
||
* import { Tuple } from "effect"
|
||
*
|
||
* const result = Tuple.omit(["a", "b", "c", "d"], [1, 3])
|
||
* console.log(result) // ["a", "c"]
|
||
* ```
|
||
*
|
||
* @see {@link pick} – the inverse (keep only specified indices)
|
||
* @category filtering
|
||
* @since 4.0.0
|
||
*/
|
||
export const omit: {
|
||
<const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>>(
|
||
indices: I
|
||
): (self: T) => OmitTuple<T, I[number]>
|
||
<const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>>(
|
||
self: T,
|
||
indices: I
|
||
): OmitTuple<T, I[number]>
|
||
} = dual(
|
||
2,
|
||
<const T extends ReadonlyArray<unknown>>(
|
||
self: T,
|
||
indices: ReadonlyArray<number>
|
||
) => {
|
||
const toDrop = new Set<number>(indices)
|
||
return self.filter((_, i) => !toDrop.has(i))
|
||
}
|
||
)
|
||
|
||
/**
|
||
* Appends a single element to the end of a tuple.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when you need the appended value to remain part of the tuple's type-level
|
||
* shape and preserve literal element positions.
|
||
*
|
||
* **Details**
|
||
*
|
||
* The result type is `[...T, E]`, preserving all existing element types.
|
||
*
|
||
* **Example** (Appending an element)
|
||
*
|
||
* ```ts
|
||
* import { pipe, Tuple } from "effect"
|
||
*
|
||
* const result = pipe(Tuple.make(1, 2), Tuple.appendElement("end"))
|
||
* console.log(result) // [1, 2, "end"]
|
||
* ```
|
||
*
|
||
* @see {@link appendElements} – append multiple elements (another tuple)
|
||
* @category combining
|
||
* @since 2.0.0
|
||
*/
|
||
export const appendElement: {
|
||
<const E>(element: E): <const T extends ReadonlyArray<unknown>>(self: T) => [...T, E]
|
||
<const T extends ReadonlyArray<unknown>, const E>(self: T, element: E): [...T, E]
|
||
} = dual(2, <T extends ReadonlyArray<unknown>, E>(self: T, element: E): [...T, E] => [...self, element])
|
||
|
||
/**
|
||
* Concatenates two tuples into a single tuple.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use to append all elements from one tuple to another tuple.
|
||
*
|
||
* **Details**
|
||
*
|
||
* The result type is `[...T1, ...T2]`, preserving all element types from both
|
||
* tuples. Neither input tuple is mutated; a fresh tuple is returned.
|
||
*
|
||
* **Example** (Concatenating tuples)
|
||
*
|
||
* ```ts
|
||
* import { pipe, Tuple } from "effect"
|
||
*
|
||
* const result = pipe(Tuple.make(1, 2), Tuple.appendElements(["a", "b"] as const))
|
||
* console.log(result) // [1, 2, "a", "b"]
|
||
* ```
|
||
*
|
||
* @see {@link appendElement} – append a single element
|
||
* @category combining
|
||
* @since 4.0.0
|
||
*/
|
||
export const appendElements: {
|
||
<const T2 extends ReadonlyArray<unknown>>(
|
||
that: T2
|
||
): <const T1 extends ReadonlyArray<unknown>>(self: T1) => [...T1, ...T2]
|
||
<const T1 extends ReadonlyArray<unknown>, const T2 extends ReadonlyArray<unknown>>(self: T1, that: T2): [...T1, ...T2]
|
||
} = dual(
|
||
2,
|
||
<T1 extends ReadonlyArray<unknown>, T2 extends ReadonlyArray<unknown>>(
|
||
self: T1,
|
||
that: T2
|
||
): [...T1, ...T2] => [...self, ...that]
|
||
)
|
||
|
||
type Evolver<T> = { readonly [I in keyof T]?: ((a: T[I]) => unknown) | undefined }
|
||
|
||
type Evolved<T, E> = { [I in keyof T]: I extends keyof E ? (E[I] extends (...a: any) => infer R ? R : T[I]) : T[I] }
|
||
|
||
/**
|
||
* Transforms elements of a tuple by providing an array of transform functions.
|
||
* Each function applies to the element at the same position. Positions beyond
|
||
* the array's length are copied unchanged.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when you want to update the first N elements while keeping the rest.
|
||
*
|
||
* **Details**
|
||
*
|
||
* Each transform function receives the current value and can return a different
|
||
* type.
|
||
*
|
||
* **Example** (Transforming selected elements)
|
||
*
|
||
* ```ts
|
||
* import { pipe, Tuple } from "effect"
|
||
*
|
||
* const result = pipe(
|
||
* Tuple.make("hello", 42, true),
|
||
* Tuple.evolve([
|
||
* (s) => s.toUpperCase(),
|
||
* (n) => n * 2
|
||
* ])
|
||
* )
|
||
* console.log(result) // ["HELLO", 84, true]
|
||
* ```
|
||
*
|
||
* @see {@link map} – apply the same transformation to all elements
|
||
* @see {@link renameIndices} – swap element positions
|
||
* @category mapping
|
||
* @since 4.0.0
|
||
*/
|
||
export const evolve: {
|
||
<const T extends ReadonlyArray<unknown>, const E extends Evolver<T>>(evolver: E): (self: T) => Evolved<T, E>
|
||
<const T extends ReadonlyArray<unknown>, const E extends Evolver<T>>(self: T, evolver: E): Evolved<T, E>
|
||
} = dual(
|
||
2,
|
||
<const T extends ReadonlyArray<unknown>, const E extends Evolver<T>>(self: T, evolver: E) => {
|
||
return self.map((e, i) => (evolver[i] !== undefined ? evolver[i](e) : e))
|
||
}
|
||
)
|
||
|
||
/**
|
||
* Renames tuple indices by providing an array of stringified source
|
||
* indices. Each position in the array specifies which index to read from
|
||
* (e.g., `["2", "1", "0"]` reverses a 3-element tuple).
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use to reorder tuple elements while preserving index-specific types.
|
||
*
|
||
* **Details**
|
||
*
|
||
* The mapping returns a tuple in the requested index order.
|
||
*
|
||
* **Gotchas**
|
||
*
|
||
* The mapping uses stringified source indices, not arbitrary names.
|
||
*
|
||
* **Example** (Swapping elements)
|
||
*
|
||
* ```ts
|
||
* import { pipe, Tuple } from "effect"
|
||
*
|
||
* const result = pipe(
|
||
* Tuple.make("a", "b", "c"),
|
||
* Tuple.renameIndices(["2", "1", "0"])
|
||
* )
|
||
* console.log(result) // ["c", "b", "a"]
|
||
* ```
|
||
*
|
||
* @see {@link evolve} – transform element values instead of positions
|
||
* @category Index utilities
|
||
* @since 4.0.0
|
||
*/
|
||
export const renameIndices: {
|
||
<const T extends ReadonlyArray<unknown>, const M extends { readonly [I in keyof T]?: `${keyof T & string}` }>(
|
||
mapping: M
|
||
): (self: T) => { [I in keyof T]: I extends keyof M ? M[I] extends keyof T ? T[M[I]] : T[I] : T[I] }
|
||
<const T extends ReadonlyArray<unknown>, const M extends { readonly [I in keyof T]?: `${keyof T & string}` }>(
|
||
self: T,
|
||
mapping: M
|
||
): { [I in keyof T]: I extends keyof M ? M[I] extends keyof T ? T[M[I]] : T[I] : T[I] }
|
||
} = dual(
|
||
2,
|
||
<const T extends ReadonlyArray<unknown>, const M extends { readonly [I in keyof T]?: `${keyof T & string}` }>(
|
||
self: T,
|
||
mapping: M
|
||
) => {
|
||
return self.map((e, i) => mapping[i] !== undefined ? self[mapping[i]] : e)
|
||
}
|
||
)
|
||
|
||
/**
|
||
* Applies a `Struct.Lambda` transformation to every element in a tuple.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when you want to apply the same transformation to every tuple element.
|
||
*
|
||
* **Details**
|
||
*
|
||
* The lambda lets the compiler track the output type for each element.
|
||
*
|
||
* **Gotchas**
|
||
*
|
||
* The lambda must be created with `Struct.lambda`; a plain function will not
|
||
* type-check.
|
||
*
|
||
* **Example** (Wrapping every element in an array)
|
||
*
|
||
* ```ts
|
||
* import { pipe, Struct, Tuple } from "effect"
|
||
*
|
||
* interface AsArray extends Struct.Lambda {
|
||
* <A>(self: A): Array<A>
|
||
* readonly "~lambda.out": Array<this["~lambda.in"]>
|
||
* }
|
||
*
|
||
* const asArray = Struct.lambda<AsArray>((a) => [a])
|
||
* const result = pipe(Tuple.make(1, "hello", true), Tuple.map(asArray))
|
||
* console.log(result) // [[1], ["hello"], [true]]
|
||
* ```
|
||
*
|
||
* @see {@link mapPick} – apply a lambda only to selected indices
|
||
* @see {@link mapOmit} – apply a lambda to all indices except selected ones
|
||
* @see {@link evolve} – apply different functions to different indices
|
||
* @category mapping
|
||
* @since 3.9.0
|
||
*/
|
||
export const map: {
|
||
<L extends Lambda>(
|
||
lambda: L
|
||
): <const T extends ReadonlyArray<unknown>>(
|
||
self: T
|
||
) => { [K in keyof T]: Apply<L, T[K]> }
|
||
<const T extends ReadonlyArray<unknown>, L extends Lambda>(
|
||
self: T,
|
||
lambda: L
|
||
): { [K in keyof T]: Apply<L, T[K]> }
|
||
} = dual(
|
||
2,
|
||
<const T extends ReadonlyArray<unknown>, L extends Function>(self: T, lambda: L) => {
|
||
return self.map((e) => lambda(e))
|
||
}
|
||
)
|
||
|
||
/**
|
||
* Applies a `Struct.Lambda` transformation only to the elements at the
|
||
* specified indices; all other elements are copied unchanged.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when you want to apply the same transformation to a subset of
|
||
* positions.
|
||
*
|
||
* **Example** (Wrapping only selected elements in arrays)
|
||
*
|
||
* ```ts
|
||
* import { pipe, Struct, Tuple } from "effect"
|
||
*
|
||
* interface AsArray extends Struct.Lambda {
|
||
* <A>(self: A): Array<A>
|
||
* readonly "~lambda.out": Array<this["~lambda.in"]>
|
||
* }
|
||
*
|
||
* const asArray = Struct.lambda<AsArray>((a) => [a])
|
||
* const result = pipe(
|
||
* Tuple.make(1, "hello", true),
|
||
* Tuple.mapPick([0, 2], asArray)
|
||
* )
|
||
* console.log(result) // [[1], "hello", [true]]
|
||
* ```
|
||
*
|
||
* @see {@link map} – apply a lambda to all elements
|
||
* @see {@link mapOmit} – apply a lambda to all elements except selected ones
|
||
* @category mapping
|
||
* @since 4.0.0
|
||
*/
|
||
export const mapPick: {
|
||
<const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>, L extends Lambda>(
|
||
indices: I,
|
||
lambda: L
|
||
): (
|
||
self: T
|
||
) => { [K in keyof T]: K extends `${I[number]}` ? Apply<L, T[K]> : T[K] }
|
||
<const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>, L extends Lambda>(
|
||
self: T,
|
||
indices: I,
|
||
lambda: L
|
||
): { [K in keyof T]: K extends `${I[number]}` ? Apply<L, T[K]> : T[K] }
|
||
} = dual(
|
||
3,
|
||
<const T extends ReadonlyArray<unknown>, L extends Function>(
|
||
self: T,
|
||
indices: ReadonlyArray<number>,
|
||
lambda: L
|
||
) => {
|
||
const toPick = new Set<number>(indices)
|
||
return self.map((e, i) => (toPick.has(i) ? lambda(e) : e))
|
||
}
|
||
)
|
||
|
||
/**
|
||
* Applies a `Struct.Lambda` transformation to all elements except those at the
|
||
* specified indices; the excluded elements are copied unchanged.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when most elements should be transformed but a few should be
|
||
* preserved.
|
||
*
|
||
* **Example** (Wrapping all elements except one in arrays)
|
||
*
|
||
* ```ts
|
||
* import { pipe, Struct, Tuple } from "effect"
|
||
*
|
||
* interface AsArray extends Struct.Lambda {
|
||
* <A>(self: A): Array<A>
|
||
* readonly "~lambda.out": Array<this["~lambda.in"]>
|
||
* }
|
||
*
|
||
* const asArray = Struct.lambda<AsArray>((a) => [a])
|
||
* const result = pipe(
|
||
* Tuple.make(1, "hello", true),
|
||
* Tuple.mapOmit([1], asArray)
|
||
* )
|
||
* console.log(result) // [[1], "hello", [true]]
|
||
* ```
|
||
*
|
||
* @see {@link map} – apply a lambda to all elements
|
||
* @see {@link mapPick} – apply a lambda only to selected indices
|
||
* @category mapping
|
||
* @since 4.0.0
|
||
*/
|
||
export const mapOmit: {
|
||
<const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>, L extends Lambda>(
|
||
indices: I,
|
||
lambda: L
|
||
): (
|
||
self: T
|
||
) => { [K in keyof T]: K extends `${I[number]}` ? T[K] : Apply<L, T[K]> }
|
||
<const T extends ReadonlyArray<unknown>, const I extends ReadonlyArray<Indices<T>>, L extends Lambda>(
|
||
self: T,
|
||
indices: I,
|
||
lambda: L
|
||
): { [K in keyof T]: K extends `${I[number]}` ? T[K] : Apply<L, T[K]> }
|
||
} = dual(
|
||
3,
|
||
<const T extends ReadonlyArray<unknown>, L extends Function>(
|
||
self: T,
|
||
indices: ReadonlyArray<number>,
|
||
lambda: L
|
||
) => {
|
||
const toOmit = new Set<number>(indices)
|
||
return self.map((e, i) => (toOmit.has(i) ? e : lambda(e)))
|
||
}
|
||
)
|
||
|
||
/**
|
||
* Creates an `Equivalence` for tuples by comparing corresponding elements
|
||
* using the provided per-position `Equivalence`s. Two tuples are equivalent
|
||
* when all their corresponding elements are equivalent.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when you need an `Equivalence` to compare tuples element-by-element.
|
||
*
|
||
* **Details**
|
||
*
|
||
* This is an alias of `Equivalence.Tuple`.
|
||
*
|
||
* **Example** (Comparing tuples for equivalence)
|
||
*
|
||
* ```ts
|
||
* import { Equivalence, Tuple } from "effect"
|
||
*
|
||
* const eq = Tuple.makeEquivalence([
|
||
* Equivalence.strictEqual<string>(),
|
||
* Equivalence.strictEqual<number>()
|
||
* ])
|
||
*
|
||
* console.log(eq(["Alice", 30], ["Alice", 30])) // true
|
||
* console.log(eq(["Alice", 30], ["Bob", 30])) // false
|
||
* ```
|
||
*
|
||
* @see {@link makeOrder} – create an `Order` for tuples
|
||
* @category instances
|
||
* @since 4.0.0
|
||
*/
|
||
export const makeEquivalence = Equivalence.Tuple
|
||
|
||
/**
|
||
* Creates an `Order` for tuples by comparing corresponding elements using the
|
||
* provided per-position `Order`s. Elements are compared left-to-right; the
|
||
* first non-zero comparison determines the result.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when you need to sort fixed-position arrays lexicographically, with each
|
||
* position using its own ordering rule.
|
||
*
|
||
* **Details**
|
||
*
|
||
* This is an alias of `Order.Tuple`.
|
||
*
|
||
* **Example** (Ordering tuples)
|
||
*
|
||
* ```ts
|
||
* import { Number, String, Tuple } from "effect"
|
||
*
|
||
* const ord = Tuple.makeOrder([String.Order, Number.Order])
|
||
*
|
||
* console.log(ord(["Alice", 30], ["Bob", 25])) // -1
|
||
* console.log(ord(["Alice", 30], ["Alice", 30])) // 0
|
||
* ```
|
||
*
|
||
* @see {@link makeEquivalence} – create an `Equivalence` for tuples
|
||
* @category ordering
|
||
* @since 4.0.0
|
||
*/
|
||
export const makeOrder = order.Tuple
|
||
|
||
export {
|
||
/**
|
||
* Checks whether an array has exactly `N` elements, narrowing the type to a
|
||
* fixed-length tuple.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use to guard that an array has exactly the tuple length expected at
|
||
* runtime.
|
||
*
|
||
* **Details**
|
||
*
|
||
* This is a re-export of `Predicate.isTupleOf`. It narrows the type to
|
||
* `TupleOf<N, T>` in the truthy branch.
|
||
*
|
||
* **Gotchas**
|
||
*
|
||
* This only checks `.length`; it does not validate element types.
|
||
*
|
||
* **Example** (Checking exact length)
|
||
*
|
||
* ```ts
|
||
* import { Tuple } from "effect"
|
||
*
|
||
* const arr: Array<number> = [1, 2, 3]
|
||
* if (Tuple.isTupleOf(arr, 3)) {
|
||
* console.log(arr)
|
||
* // ^? [number, number, number]
|
||
* }
|
||
* ```
|
||
*
|
||
* @see `isTupleOfAtLeast` – check for a minimum length
|
||
* @category guards
|
||
* @since 3.3.0
|
||
*/
|
||
isTupleOf,
|
||
/**
|
||
* Checks whether an array has at least `N` elements, narrowing the type to a
|
||
* tuple with a minimum length.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use to guard that an array has at least the tuple length expected at
|
||
* runtime.
|
||
*
|
||
* **Details**
|
||
*
|
||
* This is a re-export of `Predicate.isTupleOfAtLeast`. It narrows the type to
|
||
* `TupleOfAtLeast<N, T>` in the truthy branch.
|
||
*
|
||
* **Gotchas**
|
||
*
|
||
* This only checks `.length`; it does not validate element types.
|
||
*
|
||
* **Example** (Checking minimum length)
|
||
*
|
||
* ```ts
|
||
* import { Tuple } from "effect"
|
||
*
|
||
* const arr: Array<number> = [1, 2, 3, 4]
|
||
* if (Tuple.isTupleOfAtLeast(arr, 3)) {
|
||
* console.log(arr)
|
||
* // ^? [number, number, number, ...number[]]
|
||
* }
|
||
* ```
|
||
*
|
||
* @see `isTupleOf` – check for an exact length
|
||
* @category guards
|
||
* @since 3.3.0
|
||
*/
|
||
isTupleOfAtLeast
|
||
} from "./Predicate.ts"
|
||
|
||
/**
|
||
* Creates a `Combiner` for a tuple shape by providing a `Combiner` for each
|
||
* position. When two tuples are combined, each element is merged using its
|
||
* corresponding combiner.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when you need to merge two same-shape tuples by combining each position
|
||
* independently, such as summing counters or concatenating strings.
|
||
*
|
||
* **Example** (Combining tuple elements)
|
||
*
|
||
* ```ts
|
||
* import { Number, String, Tuple } from "effect"
|
||
*
|
||
* const C = Tuple.makeCombiner<readonly [number, string]>([
|
||
* Number.ReducerSum,
|
||
* String.ReducerConcat
|
||
* ])
|
||
*
|
||
* const result = C.combine([1, "hello"], [2, " world"])
|
||
* console.log(result) // [3, "hello world"]
|
||
* ```
|
||
*
|
||
* @see {@link makeReducer} – like `makeCombiner` but with an initial value
|
||
* @category combining
|
||
* @since 4.0.0
|
||
*/
|
||
export function makeCombiner<A extends ReadonlyArray<unknown>>(
|
||
combiners: { readonly [K in keyof A]: Combiner.Combiner<A[K]> }
|
||
): Combiner.Combiner<A> {
|
||
return Combiner.make((self, that) => {
|
||
const out = []
|
||
for (let i = 0; i < self.length; i++) {
|
||
out.push(combiners[i].combine(self[i], that[i]))
|
||
}
|
||
return out as any
|
||
})
|
||
}
|
||
|
||
/**
|
||
* Creates a `Reducer` for a tuple shape by providing a `Reducer` for each
|
||
* position. The initial value is derived from each position's
|
||
* `Reducer.initialValue`. When reducing a collection of tuples, each element
|
||
* is combined independently.
|
||
*
|
||
* **When to use**
|
||
*
|
||
* Use when you need to fold same-shape tuples by accumulating each position
|
||
* independently into one summary tuple.
|
||
*
|
||
* **Example** (Reducing a collection of tuples)
|
||
*
|
||
* ```ts
|
||
* import { Number, String, Tuple } from "effect"
|
||
*
|
||
* const R = Tuple.makeReducer<readonly [number, string]>([
|
||
* Number.ReducerSum,
|
||
* String.ReducerConcat
|
||
* ])
|
||
*
|
||
* const result = R.combineAll([
|
||
* [1, "a"],
|
||
* [2, "b"],
|
||
* [3, "c"]
|
||
* ])
|
||
* console.log(result) // [6, "abc"]
|
||
* ```
|
||
*
|
||
* @see {@link makeCombiner} – like `makeReducer` but without an initial value
|
||
* @category folding
|
||
* @since 4.0.0
|
||
*/
|
||
export function makeReducer<A extends ReadonlyArray<unknown>>(
|
||
reducers: { readonly [K in keyof A]: Reducer.Reducer<A[K]> }
|
||
): Reducer.Reducer<A> {
|
||
const combine = makeCombiner(reducers).combine
|
||
const initialValue = []
|
||
for (let i = 0; i < reducers.length; i++) {
|
||
initialValue.push(reducers[i].initialValue)
|
||
}
|
||
return Reducer.make(combine, initialValue as unknown as A)
|
||
}
|