Record, Struct & Tuple
Three small, complementary modules cover the everyday shapes of plain JavaScript
data. They all share the same design: immutable (every operation returns a
new value, the input is never mutated) and dual (call data-first as
Record.map(record, f) or data-last in a pipeline as pipe(record, Record.map(f))).
| Module | Use it for | Example shape |
|---|---|---|
Record | Homogeneous string/symbol-keyed maps — collect/map/filter over entries | Record<string, number> |
Struct | Objects with a known, heterogeneous shape — pick/omit/evolve while preserving precise types | { name: string; age: number } |
Tuple | Fixed-length positional data, each slot a different type | readonly [number, number, string] |
Reach for Record when keys are dynamic and values share one type. Reach for
Struct when the object has a fixed set of named fields you want to manipulate
without losing literal field types. Reach for Tuple when position matters and
the length is fixed.
Record — the common case
Section titled “Record — the common case”Record treats a plain object as an immutable dictionary. Lookups that might
miss return an Option; transformations allocate a new
object.
import { Record } from "effect"
const scores = { alice: 1, bob: 2 }
const next = Record.set(scores, "carol", 3)const doubled = Record.map(next, (score) => score * 2)
console.log(scores) // => { alice: 1, bob: 2 } (unchanged)console.log(doubled) // => { alice: 2, bob: 4, carol: 6 }console.log(Record.get(doubled, "alice")) // => Option.some(2)console.log(Record.get(doubled, "dave")) // => Option.none()Struct — the common case
Section titled “Struct — the common case”Struct operates on objects with a fixed shape and tracks the precise type
through each step: pick narrows to a literal subset, evolve may change a
field’s type, renameKeys updates the key names — all reflected in the result type.
import { pipe, Struct } from "effect"
const user = { firstName: "Alice", lastName: "Smith", age: 30, admin: false }
const result = pipe( user, Struct.pick(["firstName", "age"]), // { firstName: string; age: number } Struct.evolve({ age: (n) => n + 1 }), // age stays number Struct.renameKeys({ firstName: "name" }) // { name: string; age: number })
console.log(result) // => { name: "Alice", age: 31 }Tuple — the common case
Section titled “Tuple — the common case”Tuple works on fixed-length arrays where each position has its own type. The
type system tracks the type at each index, and growth operations like
appendElement widen the tuple type.
import { pipe, Tuple } from "effect"
const point = Tuple.make(10, 20) // [number, number]
const labeled = pipe( point, Tuple.appendElement("red") // [number, number, string])
console.log(labeled) // => [10, 20, "red"]console.log(Tuple.get(labeled, 2)) // => "red"Record
Section titled “Record”ReadonlyRecord<K, A> is a plain object whose keys are known by type and whose
values share a common type A. Traversal APIs (map, keys, values, …) use
Object.keys, so they visit enumerable string keys only; targeted APIs
(has, get, set, remove) also accept symbol keys.
import { Record } from "effect"ReadonlyRecord
Section titled “ReadonlyRecord”The foundational type for immutable string/symbol-keyed mappings.
import type { Record } from "effect"
type UserRecord = Record.ReadonlyRecord<"name" | "age", string | number>// { readonly name: string | number; readonly age: string | number }The ReadonlyRecord namespace also exposes type-level helpers:
ReadonlyRecord.NonLiteralKey<K> (widens literal keys to string/symbol) and
ReadonlyRecord.IntersectKeys<K1, K2> (computes overlapping keys, used by
intersection).
import type { Record } from "effect"
type K = Record.ReadonlyRecord.NonLiteralKey<"foo" | "bar"> // stringtype I = Record.ReadonlyRecord.IntersectKeys<"a" | "b", "b" | "c"> // "b"ReadonlyRecordTypeLambda
Section titled “ReadonlyRecordTypeLambda”The higher-kinded type lambda for records, used to plug records into generic
type-constructor machinery (HKT.Kind).
import type { HKT, Record } from "effect"
type Settings = HKT.Kind< Record.ReadonlyRecordTypeLambda<"port" | "retries">, never, never, never, number> // Record<"port" | "retries", number>Constructors
Section titled “Constructors”Creates a new empty record, typed for future operations.
const r = Record.empty<string, number>()console.log(r) // => {}singleton
Section titled “singleton”Creates a record from a single key/value pair.
console.log(Record.singleton("a", 1)) // => { a: 1 }fromIterableWith
Section titled “fromIterableWith”Builds a record from an iterable, mapping each element to a [key, value] tuple.
console.log(Record.fromIterableWith([1, 2, 3], (a) => [String(a), a * 2]))// => { "1": 2, "2": 4, "3": 6 }fromIterableBy
Section titled “fromIterableBy”Builds a record from an iterable, deriving each key with the provided function and storing the element as the value.
const users = [ { id: "2", name: "name2" }, { id: "1", name: "name1" }]console.log(Record.fromIterableBy(users, (u) => u.id))// => { "2": { id: "2", name: "name2" }, "1": { id: "1", name: "name1" } }fromEntries
Section titled “fromEntries”Builds a record from an iterable of [key, value] pairs (the inverse of
toEntries). Later duplicate keys overwrite earlier ones.
console.log(Record.fromEntries([["a", 1], ["b", 2]])) // => { a: 1, b: 2 }Conversions
Section titled “Conversions”collect
Section titled “collect”Transforms record entries into an array using a (key, value) mapping function.
console.log(Record.collect({ a: 1, b: 2, c: 3 }, (key, n) => [key, n]))// => [["a", 1], ["b", 2], ["c", 3]]toEntries
Section titled “toEntries”Returns the record’s entries as an array of [key, value] tuples.
console.log(Record.toEntries({ a: 1, b: 2, c: 3 }))// => [["a", 1], ["b", 2], ["c", 3]]Returns the record’s string keys as an array.
console.log(Record.keys({ a: 1, b: 2, c: 3 })) // => ["a", "b", "c"]values
Section titled “values”Returns the record’s values as an array.
console.log(Record.values({ a: 1, b: 2, c: 3 })) // => [1, 2, 3]Querying
Section titled “Querying”Returns the number of string-keyed entries.
console.log(Record.size({ a: "a", b: 1, c: true })) // => 3Checks whether a key exists (works with symbol keys too).
console.log(Record.has({ a: 1, b: 2 }, "a")) // => trueconsole.log(Record.has({ a: 1, b: 2 }, "c")) // => falseRetrieves a value safely as an Option.
import { Record } from "effect"
const person: Record<string, unknown> = { name: "John Doe", age: 35 }
console.log(Record.get(person, "name")) // => Option.some("John Doe")console.log(Record.get(person, "email")) // => Option.none()isEmptyRecord
Section titled “isEmptyRecord”Type guard: true when a mutable record has no keys.
console.log(Record.isEmptyRecord({})) // => trueconsole.log(Record.isEmptyRecord({ a: 3 })) // => falseisEmptyReadonlyRecord
Section titled “isEmptyReadonlyRecord”The same guard, typed for ReadonlyRecord.
console.log(Record.isEmptyReadonlyRecord({})) // => trueconsole.log(Record.isEmptyReadonlyRecord({ a: 3 })) // => falsefindFirst
Section titled “findFirst”Returns the first [key, value] entry satisfying a predicate, as an Option.
console.log( Record.findFirst({ a: 1, b: 2, c: 3 }, (value, key) => value > 1 && key !== "b"))// => Option.some(["c", 3])Checks whether every entry satisfies the predicate (also acts as a type guard when given a refinement).
console.log(Record.every({ a: 1, b: 2 }, (n) => n > 0)) // => trueconsole.log(Record.every({ a: 1, b: -1 }, (n) => n > 0)) // => falseChecks whether at least one entry satisfies the predicate.
console.log(Record.some({ a: 1, b: 2 }, (n) => n > 1)) // => trueconsole.log(Record.some({ a: 1, b: 2 }, (n) => n > 2)) // => falseModifying
Section titled “Modifying”All modifying APIs return new records. The ones that might miss a key return an
Option.
Adds or updates a key, returning a new record.
console.log(Record.set({ a: 1, b: 2 }, "a", 5)) // => { a: 5, b: 2 }console.log(Record.set({ a: 1, b: 2 }, "c", 5)) // => { a: 1, b: 2, c: 5 }modify
Section titled “modify”Applies a function to the value at a key, or returns Option.none() if absent.
const input: Record<string, number> = { a: 3 }console.log(Record.modify(input, "a", (x) => x * 2)) // => Option.some({ a: 6 })console.log(Record.modify(input, "b", (x) => x * 2)) // => Option.none()replace
Section titled “replace”Replaces the value at an existing key; Option.none() if the key is absent.
console.log(Record.replace({ a: 1, b: 2 }, "a", 10)) // => Option.some({ a: 10, b: 2 })console.log(Record.replace(Record.empty<string>(), "a", 10)) // => Option.none()remove
Section titled “remove”Returns a shallow copy without the given key.
console.log(Record.remove({ a: 1, b: 2 }, "a")) // => { b: 2 }Removes a key and returns Option<[value, restOfRecord]>.
const input: Record<string, number> = { a: 1, b: 2 }console.log(Record.pop(input, "a")) // => Option.some([1, { b: 2 }])console.log(Record.pop(input, "c")) // => Option.none()Maps over the values (the callback also receives the key).
console.log(Record.map({ a: 3, b: 5 }, (n) => `-${n}`)) // => { a: "-3", b: "-5" }console.log(Record.map({ a: 3, b: 5 }, (n, key) => `${key}-${n}`))// => { a: "a-3", b: "b-5" }mapKeys
Section titled “mapKeys”Maps over the keys, preserving values.
console.log(Record.mapKeys({ a: 3, b: 5 }, (key) => key.toUpperCase()))// => { A: 3, B: 5 }mapEntries
Section titled “mapEntries”Maps both keys and values at once, returning a new [key, value] per entry.
console.log(Record.mapEntries({ a: 3, b: 5 }, (a, key) => [key.toUpperCase(), a + 1]))// => { A: 4, B: 6 }filterMap
Section titled “filterMap”Maps and filters in one pass using Result: Result.succeed
keeps the value, Result.failVoid drops the entry.
import { Record, Result } from "effect"
const f = (a: number) => (a > 2 ? Result.succeed(a * 2) : Result.failVoid)console.log(Record.filterMap({ a: 1, b: 2, c: 3 }, f)) // => { c: 6 }filter
Section titled “filter”Keeps entries whose value matches the predicate (or refinement).
console.log(Record.filter({ a: 1, b: 2, c: 3, d: 4 }, (n) => n > 2)) // => { c: 3, d: 4 }partition
Section titled “partition”Splits entries into [failures, successes] by applying a Result-returning
function.
import { Record, Result } from "effect"
const f = (n: number) => (n % 2 === 0 ? Result.succeed(n) : Result.fail(n))console.log(Record.partition({ a: 1, b: 2, c: 3 }, f)) // => [{ a: 1, c: 3 }, { b: 2 }]separate
Section titled “separate”Splits a record of Result values into [failures, successes].
import { Record, Result } from "effect"
console.log(Record.separate({ a: Result.fail("e"), b: Result.succeed(1) }))// => [{ a: "e" }, { b: 1 }]getSomes
Section titled “getSomes”Keeps only the Some values from a record of Options.
import { Option, Record } from "effect"
console.log(Record.getSomes({ a: Option.some(1), b: Option.none(), c: Option.some(2) }))// => { a: 1, c: 2 }getFailures
Section titled “getFailures”Keeps only the failures from a record of Results.
import { Record, Result } from "effect"
console.log( Record.getFailures({ a: Result.succeed(1), b: Result.fail("err"), c: Result.succeed(2) }))// => { b: "err" }getSuccesses
Section titled “getSuccesses”Keeps only the successes from a record of Results.
import { Record, Result } from "effect"
console.log( Record.getSuccesses({ a: Result.succeed(1), b: Result.fail("err"), c: Result.succeed(2) }))// => { a: 1, c: 2 }reduce
Section titled “reduce”Folds the entries into a single accumulated value.
console.log(Record.reduce({ a: 1, b: 2, c: 3 }, 0, (acc, value) => acc + value)) // => 6Combining & comparing
Section titled “Combining & comparing”Merges two records, keeping keys from both; overlapping keys are merged with the combine function.
console.log(Record.union({ a: 1, b: 2 }, { b: 3, c: 4 }, (a, b) => a + b))// => { a: 1, b: 5, c: 4 }intersection
Section titled “intersection”Keeps only keys present in both records, merging their values.
console.log(Record.intersection({ a: 1, b: 2 }, { b: 3, c: 4 }, (a, b) => a + b))// => { b: 5 }difference
Section titled “difference”Keeps only keys unique to each record; shared keys are dropped.
console.log(Record.difference({ a: 1, b: 2 }, { b: 3, c: 4 })) // => { a: 1, c: 4 }isSubrecord
Section titled “isSubrecord”Checks whether every key/value of self appears in that, comparing values
with Effect equality.
console.log(Record.isSubrecord({ a: 1 } as Record<string, number>, { a: 1, b: 2 })) // => trueconsole.log(Record.isSubrecord({ a: 1, b: 2 }, { a: 1 } as Record<string, number>)) // => falseisSubrecordBy
Section titled “isSubrecordBy”Like isSubrecord, but uses a supplied Equivalence to
compare values.
import { Equivalence, Record } from "effect"
const isSub = Record.isSubrecordBy( Equivalence.make<string>((a, b) => a.toLowerCase() === b.toLowerCase()))console.log(isSub({ role: "Admin" }, { role: "admin", status: "active" })) // => truemakeEquivalence
Section titled “makeEquivalence”Builds an Equivalence for records from an Equivalence for values. Two records
are equivalent when they have the same keys and equivalent values.
import { Equal, Record } from "effect"
const eq = Record.makeEquivalence(Equal.asEquivalence<number>())console.log(eq({ a: 1, b: 2 }, { a: 1, b: 2 })) // => trueconsole.log(eq({ a: 1, b: 2 }, { a: 1, b: 3 })) // => falsemakeReducerUnion
Section titled “makeReducerUnion”Builds a Reducer that folds many records into one with union semantics,
combining overlapping values with the given Combiner.
import { Number as Num, Record } from "effect"
const reducer = Record.makeReducerUnion<string, number>(Num.ReducerSum)console.log(reducer.combine({ a: 1, b: 2 }, { b: 3, c: 4 })) // => { a: 1, b: 5, c: 4 }makeReducerIntersection
Section titled “makeReducerIntersection”Builds a Reducer whose combine intersects two records and combines shared
values. Note: because the reducer’s initialValue is {}, the default
combineAll folds from an empty record and yields {} for ordinary inputs — use
combine directly for pairs.
import { Number as Num, Record } from "effect"
const reducer = Record.makeReducerIntersection<string, number>(Num.ReducerSum)console.log(reducer.combine({ a: 1, b: 2 }, { b: 3, c: 4 })) // => { b: 5 }Struct
Section titled “Struct”Struct manipulates objects with a fixed, heterogeneous shape, preserving exact
field types through every operation. Functions iterate with for...in, so
inherited enumerable properties are included; keys returns string keys only.
import { Struct } from "effect"Utility types
Section titled “Utility types”Simplify
Section titled “Simplify”Flattens an intersection like A & B into a single readable object type. Purely
cosmetic; preserves readonly.
import type { Struct } from "effect"
type Simplified = Struct.Simplify<{ a: string } & { b: number }>// { a: string; b: number }Mutable
Section titled “Mutable”Strips readonly modifiers (and flattens, like Simplify).
import type { Struct } from "effect"
type Writable = Struct.Mutable<{ readonly a: string; readonly b: number }>// { a: string; b: number }Assign
Section titled “Assign”The type-level equivalent of { ...T, ...U }: merges two object types with the
right side winning on overlapping keys.
import type { Struct } from "effect"
type Merged = Struct.Assign<{ a: string; b: number }, { b: boolean; c: string }>// { a: string; b: boolean; c: string }Operations
Section titled “Operations”Extracts a single property; the return type is narrowed to S[K].
import { pipe, Struct } from "effect"
console.log(pipe({ name: "Alice", age: 30 }, Struct.get("name"))) // => "Alice"Returns the string keys of a struct, typed as Array<keyof S & string>. Symbol
keys are excluded.
import { Struct } from "effect"
const user = { name: "Alice", age: 30, [Symbol.for("id")]: 1 }console.log(Struct.keys(user)) // => ["name", "age"]Creates a new struct with only the named keys — and preserves the literal field types of those keys.
import { pipe, Struct } from "effect"
const user = { name: "Alice", age: 30, admin: true }const result = pipe(user, Struct.pick(["name", "age"]))// result: { name: string; age: number }console.log(result) // => { name: "Alice", age: 30 }The inverse of pick: a new struct with the named keys removed, types preserved.
import { pipe, Struct } from "effect"
const user = { name: "Alice", age: 30, password: "secret" }console.log(pipe(user, Struct.omit(["password"]))) // => { name: "Alice", age: 30 }assign
Section titled “assign”Runtime { ...self, ...that } with proper Assign typing — that wins on
overlapping keys.
import { pipe, Struct } from "effect"
const defaults = { theme: "light", lang: "en" }const overrides = { theme: "dark", fontSize: 14 }console.log(pipe(defaults, Struct.assign(overrides)))// => { theme: "dark", lang: "en", fontSize: 14 }evolve
Section titled “evolve”Transforms selected values with per-key functions; untouched keys are copied. Each function may change the field’s type, and the result type tracks that.
import { pipe, Struct } from "effect"
const result = pipe( { name: "alice", age: 30, active: true }, Struct.evolve({ name: (s) => s.toUpperCase(), age: (n) => n + 1 }))console.log(result) // => { name: "ALICE", age: 31, active: true }evolveKeys
Section titled “evolveKeys”Transforms selected keys with per-key functions; values are preserved.
import { pipe, Struct } from "effect"
console.log(pipe({ name: "Alice", age: 30 }, Struct.evolveKeys({ name: (k) => k.toUpperCase() })))// => { NAME: "Alice", age: 30 }evolveEntries
Section titled “evolveEntries”Transforms both keys and values together: each function receives (key, value)
and returns [newKey, newValue].
import { pipe, Struct } from "effect"
const result = pipe( { amount: 100, label: "total" }, Struct.evolveEntries({ amount: (k, v) => [`${k}Cents`, v * 100], label: (k, v) => [k, v.toUpperCase()] }))console.log(result) // => { amountCents: 10000, label: "TOTAL" }renameKeys
Section titled “renameKeys”Declarative key renaming with a static { oldKey: newKey } mapping; unmentioned
keys are copied unchanged.
import { pipe, Struct } from "effect"
console.log( pipe( { firstName: "Alice", lastName: "Smith", age: 30 }, Struct.renameKeys({ firstName: "first", lastName: "last" }) ))// => { first: "Alice", last: "Smith", age: 30 }Typed mapping (Lambda)
Section titled “Typed mapping (Lambda)”map, mapPick, and mapOmit apply a single transformation to many fields. So
the compiler can track how each field’s type changes, the transformation must be
a Lambda value created with Struct.lambda — a plain function will not
type-check.
Lambda
Section titled “Lambda”The type-level function interface. Extend it with concrete ~lambda.in /
~lambda.out types to describe how values are transformed.
import type { Struct } from "effect"
interface ToString extends Struct.Lambda { readonly "~lambda.out": string}Computes the output type a Lambda produces for a given input type.
import type { Struct } from "effect"
interface ToString extends Struct.Lambda { readonly "~lambda.out": string}type Result = Struct.Apply<ToString, number> // stringlambda
Section titled “lambda”Wraps a plain function as a Lambda value for use with map / mapPick /
mapOmit. At runtime it is the same function; only the type changes.
import { Struct } 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])console.log(asArray(1)) // => [1]Applies a Lambda to every value in the struct.
import { pipe, Struct } 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])console.log(pipe({ width: 10, height: 20 }, Struct.map(asArray)))// => { width: [10], height: [20] }mapPick
Section titled “mapPick”Applies a Lambda only to the selected keys; the rest are copied unchanged.
import { pipe, Struct } 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])console.log(pipe({ x: 1, y: 2, z: 3 }, Struct.mapPick(["x", "z"], asArray)))// => { x: [1], y: 2, z: [3] }mapOmit
Section titled “mapOmit”Applies a Lambda to all keys except the selected ones.
import { pipe, Struct } 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])console.log(pipe({ x: 1, y: 2, z: 3 }, Struct.mapOmit(["y"], asArray)))// => { x: [1], y: 2, z: [3] }Instances
Section titled “Instances”makeEquivalence
Section titled “makeEquivalence”Builds an Equivalence for a struct from a per-field
Equivalence. (Alias of Equivalence.Struct.)
import { Equivalence, Struct } from "effect"
const PersonEq = Struct.makeEquivalence({ name: Equivalence.strictEqual<string>(), age: Equivalence.strictEqual<number>()})console.log(PersonEq({ name: "Alice", age: 30 }, { name: "Alice", age: 30 })) // => trueconsole.log(PersonEq({ name: "Alice", age: 30 }, { name: "Bob", age: 30 })) // => falsemakeOrder
Section titled “makeOrder”Builds an Order for a struct from a per-field Order. Fields
are compared in declaration order; the first non-zero result wins. (Alias of
Order.Struct.)
import { Number as Num, String as Str, Struct } from "effect"
const PersonOrder = Struct.makeOrder({ name: Str.Order, age: Num.Order })console.log(PersonOrder({ name: "Alice", age: 30 }, { name: "Bob", age: 25 })) // => -1makeCombiner
Section titled “makeCombiner”Builds a Combiner for a struct from a per-field
Combiner; combining two structs merges each field with its combiner. Pass
omitKeyWhen to drop fields whose merged value matches a predicate.
import { Number as Num, String as Str, Struct } from "effect"
const C = Struct.makeCombiner<{ readonly n: number; readonly s: string }>({ n: Num.ReducerSum, s: Str.ReducerConcat})console.log(C.combine({ n: 1, s: "hello" }, { n: 2, s: " world" }))// => { n: 3, s: "hello world" }makeReducer
Section titled “makeReducer”Like makeCombiner, but each field’s initial value comes from its
Reducer.initialValue, so you can fold a whole collection of structs into one.
import { Number as Num, String as Str, Struct } from "effect"
const R = Struct.makeReducer<{ readonly n: number; readonly s: string }>({ n: Num.ReducerSum, s: Str.ReducerConcat})console.log(R.combineAll([{ n: 1, s: "a" }, { n: 2, s: "b" }, { n: 3, s: "c" }]))// => { n: 6, s: "abc" }Record
Section titled “Record”A constructor (re-exported under Struct) that builds an object assigning the
same value to each of the given keys.
import { Struct } from "effect"
console.log(Struct.Record(["a", "b"], "value")) // => { a: "value", b: "value" }Tuple works on fixed-length readonly arrays where each position can have a
different type. Element access is by numeric index, and the type system tracks
the type at every slot.
import { Tuple } from "effect"Creates a properly typed tuple from its arguments (instead of [...] as const).
import { Tuple } from "effect"
console.log(Tuple.make(10, 20, "red")) // => [10, 20, "red"]Extracts the element at a given index; the index is constrained to valid positions and the return type tracks the slot’s type.
import { pipe, Tuple } from "effect"
console.log(pipe(Tuple.make(1, true, "hello"), Tuple.get(2))) // => "hello"Selects elements by index; result order matches the provided indices.
import { Tuple } from "effect"
console.log(Tuple.pick(["a", "b", "c", "d"], [0, 2, 3])) // => ["a", "c", "d"]Removes elements by index; the rest keep their original order.
import { Tuple } from "effect"
console.log(Tuple.omit(["a", "b", "c", "d"], [1, 3])) // => ["a", "c"]appendElement
Section titled “appendElement”Appends one element to the end, growing the tuple type to [...T, E].
import { pipe, Tuple } from "effect"
const result = pipe(Tuple.make(1, 2), Tuple.appendElement("end"))// result type: [number, number, string]console.log(result) // => [1, 2, "end"]appendElements
Section titled “appendElements”Concatenates two tuples into [...T1, ...T2], preserving all element types.
import { pipe, Tuple } from "effect"
console.log(pipe(Tuple.make(1, 2), Tuple.appendElements(["a", "b"] as const)))// => [1, 2, "a", "b"]evolve
Section titled “evolve”Transforms elements by position using an array of functions; positions beyond the array are copied unchanged, and each function may change its element’s type.
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]renameIndices
Section titled “renameIndices”Reorders elements by providing an array of stringified source indices (e.g.
["2", "1", "0"] reverses a 3-tuple), preserving each slot’s type.
import { pipe, Tuple } from "effect"
console.log(pipe(Tuple.make("a", "b", "c"), Tuple.renameIndices(["2", "1", "0"])))// => ["c", "b", "a"]Applies a Struct.Lambda to every element. As with Struct.map, the lambda must
be created with Struct.lambda.
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])console.log(pipe(Tuple.make(1, "hello", true), Tuple.map(asArray)))// => [[1], ["hello"], [true]]mapPick
Section titled “mapPick”Applies a Struct.Lambda only to the selected indices; other elements are copied.
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])console.log(pipe(Tuple.make(1, "hello", true), Tuple.mapPick([0, 2], asArray)))// => [[1], "hello", [true]]mapOmit
Section titled “mapOmit”Applies a Struct.Lambda to all indices except the selected ones.
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])console.log(pipe(Tuple.make(1, "hello", true), Tuple.mapOmit([1], asArray)))// => [[1], "hello", [true]]makeEquivalence
Section titled “makeEquivalence”Builds an Equivalence for tuples from per-position
Equivalences. (Alias of Equivalence.Tuple.)
import { Equivalence, Tuple } from "effect"
const eq = Tuple.makeEquivalence([ Equivalence.strictEqual<string>(), Equivalence.strictEqual<number>()])console.log(eq(["Alice", 30], ["Alice", 30])) // => trueconsole.log(eq(["Alice", 30], ["Bob", 30])) // => falsemakeOrder
Section titled “makeOrder”Builds an Order for tuples from per-position Orders,
compared left to right. (Alias of Order.Tuple.)
import { Number as Num, String as Str, Tuple } from "effect"
const ord = Tuple.makeOrder([Str.Order, Num.Order])console.log(ord(["Alice", 30], ["Bob", 25])) // => -1console.log(ord(["Alice", 30], ["Alice", 30])) // => 0makeCombiner
Section titled “makeCombiner”Builds a Combiner for a tuple from per-position Combiners; combining merges
each element with its combiner.
import { Number as Num, String as Str, Tuple } from "effect"
const C = Tuple.makeCombiner<readonly [number, string]>([Num.ReducerSum, Str.ReducerConcat])console.log(C.combine([1, "hello"], [2, " world"])) // => [3, "hello world"]makeReducer
Section titled “makeReducer”Like makeCombiner, but derives the initial value from each position’s
Reducer.initialValue, letting you fold a collection of tuples.
import { Number as Num, String as Str, Tuple } from "effect"
const R = Tuple.makeReducer<readonly [number, string]>([Num.ReducerSum, Str.ReducerConcat])console.log(R.combineAll([[1, "a"], [2, "b"], [3, "c"]])) // => [6, "abc"]isTupleOf
Section titled “isTupleOf”Runtime guard re-exported from Predicate: checks an array has exactly N
elements, narrowing it to a fixed-length tuple (length only, not element types).
import { Tuple } from "effect"
const arr: Array<number> = [1, 2, 3]if (Tuple.isTupleOf(arr, 3)) { console.log(arr) // arr: [number, number, number]}isTupleOfAtLeast
Section titled “isTupleOfAtLeast”Runtime guard re-exported from Predicate: checks an array has at least N
elements, narrowing to a tuple with a minimum length.
import { Tuple } from "effect"
const arr: Array<number> = [1, 2, 3, 4]if (Tuple.isTupleOfAtLeast(arr, 3)) { console.log(arr) // arr: [number, number, number, ...number[]]}