Tuple — Typed Pairs
We frequently encounter data that naturally belongs together in pairs: a configuration key and its value, a product SKU and its price, or a coordinate of latitude and longitude.
In standard JavaScript and TypeScript, we represent these associations using either small object literals or native two-element arrays (tuples). However, when we need to transform only one side of a pair within a functional pipeline — such as converting a price from cents to dollars while preserving the SKU — we are forced to write verbose destructuring and reconstruction code:
This inline destructuring introduces visual noise, forces us to declare temporary local variables, and breaks the clean flow of point-free composition.
Tuple treats typed pairs (readonly [A, B]) as first-class, unified containers. It provides
data-last operations to transform either side independently, modify both sides at once, or collapse
the pair into a single value, ensuring that paired values travel cleanly through pipelines.
The problem with manual tuple destructuring
Section titled “The problem with manual tuple destructuring”Suppose we are building a localization system that formats currency amounts based on a user’s
locale. We represent this as a pair of [locale, cents]:
If we want to apply a discount to the cents, convert them to a decimal amount, and then format them
using the native Intl.NumberFormat API, an imperative implementation must constantly unpack and
pack the pair:
While functional programming encourages us to compose small, single-responsibility steps, manual array destructuring forces us to manage the internal indices of the array at each stage, leading to rigid code that is difficult to refactor.
The shift to first-class pairs
Section titled “The shift to first-class pairs”A Tuple<A, B> is a structural alias for a readonly [A, B] array. Because it is a plain
TypeScript tuple under the hood, any native two-element array is automatically a valid Tuple.
Tuple provides dedicated, pure operations that manipulate this structure without requiring us to
unpack it manually until the very end of the pipeline.
flowchart TD
A["Tuple(A, B)"] --> B["mapFirst(f)"]
B --> C["Tuple(A2, B)"]
C --> D["mapSecond(g)"]
D --> E["Tuple(A2, B2)"]
E --> F["fold(h)"]
F --> G["Final Value"]
Creating pairs
Section titled “Creating pairs”To lift two distinct values into a typed pair, we use Tuple.make:
Any standard two-element array (such as those returned by Object.entries(), Arr.zip, or
Arr.splitAt) is already structurally compatible with Tuple and requires no constructor wrapping.
Reading and swapping elements
Section titled “Reading and swapping elements”We can extract elements from a pair using Tuple.first and Tuple.second, or reverse their order
using Tuple.swap:
Transforming one or both sides
Section titled “Transforming one or both sides”Tuple allows us to apply mapping functions to either element independently without affecting the
other, or to transform both elements simultaneously:
Collapsing a pair into a single value
Section titled “Collapsing a pair into a single value”When we reach the edge of our pipeline and need to consume both values to produce a single result,
we use Tuple.fold. It applies a binary function that merges the two elements:
Side effects and conversion
Section titled “Side effects and conversion”If we need to inspect the values inside a pipeline without altering them (e.g. for logging), we can
use Tuple.tap. When interfacing with APIs that do not support tuple types, we can convert the pair
to a plain array using Tuple.toArray:
When to use Tuple vs native destructuring
Section titled “When to use Tuple vs native destructuring”Use Tuple when
Section titled “Use Tuple when”- Two values travel together as a single unit through a multi-step functional pipeline.
- You need to perform conditional or sequential maps on either side of the pair using curried
operations in
pipe. - You are consuming the output of array zip operations (
Arr.zip) and need to project or merge the resulting pairs. - You want to transition cleanly from a pair to a single collapsed value using
fold.
Use native destructuring when
Section titled “Use native destructuring when”- The scope of the pair is local, short-lived, and you are only performing a single operation on the
values. For example,
const [x, y] = coordinates; return x + y;is simple and direct.