Equivalence.ts
Equivalence.ts overview
Section titled “Equivalence.ts overview”Defines reusable equality functions for values of the same type.
An Equivalence<A> returns true when two A values should be treated as
the same for a particular purpose. This module includes strict equality
instances for primitive types, constructors for custom comparisons, and
helpers for tuples, arrays, structs, records, dates, and values compared
through a derived field.
Since v2.0.0
Exports Grouped by Category
Section titled “Exports Grouped by Category”combinators
Section titled “combinators”Creates an equivalence for arrays where all elements are compared using the same equivalence.
When to use
Use when you need to compare arrays with one equivalence for every element.
Details
- Requires arrays to have the same length; different lengths are never equivalent
- Compares elements positionally, such as index
0with index0 - Returns
trueonly if all corresponding elements are equivalent - Empty arrays are considered equivalent
- The result is also an equivalence that satisfies reflexive, symmetric, and transitive properties
Example (Comparing number arrays)
import { Equivalence } from "effect"
const numberArrayEq = Equivalence.Array(Equivalence.strictEqual<number>())
console.log(numberArrayEq([1, 2, 3], [1, 2, 3])) // trueconsole.log(numberArrayEq([1, 2, 3], [1, 2, 4])) // falseconsole.log(numberArrayEq([1, 2], [1, 2, 3])) // false (different length)Example (Case-insensitive string array)
import { Equivalence } from "effect"
const caseInsensitive = Equivalence.mapInput(Equivalence.strictEqual<string>(), (s: string) => s.toLowerCase())const stringArrayEq = Equivalence.Array(caseInsensitive)
console.log(stringArrayEq(["Hello", "World"], ["HELLO", "WORLD"])) // trueconsole.log(stringArrayEq(["Hello"], ["Hi"])) // falseconsole.log(stringArrayEq([], [])) // true (empty arrays)See
TupleRecord
Signature
declare const Array: <A>(item: Equivalence<A>) => Equivalence<ReadonlyArray<A>>Since v4.0.0
Record
Section titled “Record”Creates an equivalence for objects by comparing all properties using the same equivalence.
When to use
Use when you need to compare records with the same equivalence for every property value.
Details
- Compares all properties present in both objects
- Requires both objects to have the same set of keys; different keys result in
false - All property values must be equivalent according to the provided equivalence
- Supports both string and symbol keys via
Reflect.ownKeys - Empty objects are considered equivalent
- The result is also an equivalence that satisfies reflexive, symmetric, and transitive properties
Example (Defining records with string values)
import { Equivalence } from "effect"
const stringRecordEq = Equivalence.Record(Equivalence.strictEqual<string>())
const record1 = { a: "hello", b: "world" }const record2 = { a: "hello", b: "world" }const record3 = { a: "hello", b: "different" }const record4 = { a: "hello" } // missing key 'b'
console.log(stringRecordEq(record1, record2)) // trueconsole.log(stringRecordEq(record1, record3)) // falseconsole.log(stringRecordEq(record1, record4)) // false (different keys)Example (Defining records with number values)
import { Equivalence } from "effect"
const numberRecordEq = Equivalence.Record(Equivalence.strictEqual<number>())
const scores1 = { alice: 100, bob: 85 }const scores2 = { alice: 100, bob: 85 }const scores3 = { alice: 100, bob: 90 }
console.log(numberRecordEq(scores1, scores2)) // trueconsole.log(numberRecordEq(scores1, scores3)) // falseSignature
declare const Record: <A>(value: Equivalence<A>) => Equivalence<Record<PropertyKey, A>>Since v4.0.0
Struct
Section titled “Struct”Creates an equivalence for objects by comparing their properties using provided equivalences.
When to use
Use when you need an Equivalence for objects with known, fixed property
names.
Details
Compares only the properties specified in the struct definition; other
properties are ignored. String and symbol keys are supported via
Reflect.ownKeys. The result returns true only if all specified properties
are equivalent according to their equivalences, and it also satisfies
reflexive, symmetric, and transitive properties.
Example (Comparing structs with different equivalences per field)
import { Equivalence } from "effect"
interface Person { name: string age: number email: string}
const caseInsensitive = Equivalence.mapInput(Equivalence.strictEqual<string>(), (s: string) => s.toLowerCase())
const personEq = Equivalence.Struct({ name: caseInsensitive, age: Equivalence.strictEqual<number>(), email: caseInsensitive})
const person1 = { name: "Alice", age: 30, email: "alice@example.com" }const person2 = { name: "ALICE", age: 30, email: "ALICE@EXAMPLE.COM" }const person3 = { name: "Alice", age: 31, email: "alice@example.com" }
console.log(personEq(person1, person2)) // true (case-insensitive match)console.log(personEq(person1, person3)) // false (different age)Example (Comparing specific fields)
import { Equivalence } from "effect"
const nameAgeEq = Equivalence.Struct({ name: Equivalence.strictEqual<string>(), age: Equivalence.strictEqual<number>()})
// Only compares name and age, ignores other propertiesconst obj1 = { name: "Alice", age: 30, extra: "ignored" }const obj2 = { name: "Alice", age: 30, extra: "different" }console.log(nameAgeEq(obj1, obj2)) // trueSee
RecordmapInputcombine
Signature
declare const Struct: <R extends Record<string, Equivalence<any>>>( fields: R) => Equivalence<{ readonly [K in keyof R]: [R[K]] extends [Equivalence<infer A>] ? A : never }>Since v4.0.0
Creates an equivalence for tuples with heterogeneous element types.
When to use
Use when you need an Equivalence for fixed-length tuples with per-position
equivalences.
Details
Tuples must have the same length; different lengths are never equivalent.
Each equivalence is applied to the corresponding element position. The result
returns true only if all elements are equivalent according to their
respective equivalences, and it also satisfies reflexive, symmetric, and
transitive properties.
Example (Comparing homogeneous tuples)
import { Equivalence } from "effect"
const stringTupleEq = Equivalence.Tuple([ Equivalence.strictEqual<string>(), Equivalence.strictEqual<string>(), Equivalence.strictEqual<string>()])
const tuple1 = ["hello", "world", "test"] as constconst tuple2 = ["hello", "world", "test"] as constconst tuple3 = ["hello", "world", "different"] as const
console.log(stringTupleEq(tuple1, tuple2)) // trueconsole.log(stringTupleEq(tuple1, tuple3)) // false (different third element)Example (Comparing tuples with custom equivalences)
import { Equivalence } from "effect"
const caseInsensitive = Equivalence.mapInput(Equivalence.strictEqual<string>(), (s: string) => s.toLowerCase())
const customTupleEq = Equivalence.Tuple([caseInsensitive, caseInsensitive, caseInsensitive])
console.log(customTupleEq(["Hello", "World", "Test"], ["HELLO", "WORLD", "TEST"])) // trueSignature
declare const Tuple: <const Elements extends ReadonlyArray<Equivalence<any>>>( elements: Elements) => Equivalence<{ readonly [I in keyof Elements]: [Elements[I]] extends [Equivalence<infer A>] ? A : never }>Since v4.0.0
combining
Section titled “combining”combine
Section titled “combine”Combines two equivalence relations using logical AND.
When to use
Use when you need to combine exactly two equivalences with AND semantics.
Details
Returns true only if both equivalences return true. The comparison
short-circuits when the first equivalence returns false. The result is also
an equivalence that satisfies reflexive, symmetric, and transitive
properties.
Example (Combining name and age equivalences)
import { Equivalence } from "effect"
interface Person { name: string age: number}
const nameEquivalence = Equivalence.mapInput(Equivalence.strictEqual<string>(), (p: Person) => p.name)
const ageEquivalence = Equivalence.mapInput(Equivalence.strictEqual<number>(), (p: Person) => p.age)
const personEquivalence = Equivalence.combine(nameEquivalence, ageEquivalence)
const person1 = { name: "Alice", age: 30 }const person2 = { name: "Alice", age: 30 }const person3 = { name: "Alice", age: 31 }
console.log(personEquivalence(person1, person2)) // trueconsole.log(personEquivalence(person1, person3)) // false (different age)See
combineAllmapInput
Signature
declare const combine: { <A>(that: Equivalence<A>): (self: Equivalence<A>) => Equivalence<A> <A>(self: Equivalence<A>, that: Equivalence<A>): Equivalence<A>}Since v2.0.0
combineAll
Section titled “combineAll”Combines multiple equivalence relations into a single equivalence using logical AND.
When to use
Use when you need to combine many Equivalence instances from an iterable.
Details
Returns true only if all equivalences in the collection return true. The
comparison stops at the first equivalence that returns false. Empty
collections return an equivalence that always returns true. The result is
also an equivalence that satisfies reflexive, symmetric, and transitive
properties.
Example (Combining multiple field equivalences)
import { Equivalence } from "effect"
interface Point3D { x: number y: number z: number}
const xEq = Equivalence.mapInput(Equivalence.strictEqual<number>(), (p: Point3D) => p.x)const yEq = Equivalence.mapInput(Equivalence.strictEqual<number>(), (p: Point3D) => p.y)const zEq = Equivalence.mapInput(Equivalence.strictEqual<number>(), (p: Point3D) => p.z)
const point3DEq = Equivalence.combineAll([xEq, yEq, zEq])
const point1 = { x: 1, y: 2, z: 3 }const point2 = { x: 1, y: 2, z: 3 }const point3 = { x: 1, y: 2, z: 4 }
console.log(point3DEq(point1, point2)) // trueconsole.log(point3DEq(point1, point3)) // false (different z)Example (Handling empty collections)
import { Equivalence } from "effect"
// Empty collection always returns trueconst alwaysEq = Equivalence.combineAll([])console.log(alwaysEq("anything", "else")) // trueSee
combinemapInput
Signature
declare const combineAll: <A>(collection: Iterable<Equivalence<A>>) => Equivalence<A>Since v2.0.0
constructors
Section titled “constructors”Creates a custom equivalence relation with an optimized reference equality check.
When to use
Use when you need an equality rule that the built-in instances and input mapping helpers cannot express, and you can provide a law-abiding comparison.
Details
The returned equivalence first checks reference equality (===) for
performance. If the values are not the same reference, it falls back to the
provided equivalence function, which must satisfy reflexive, symmetric, and
transitive properties.
Example (Case-insensitive string equivalence)
import { Equivalence } from "effect"
const caseInsensitive = Equivalence.make<string>((a, b) => a.toLowerCase() === b.toLowerCase())
console.log(caseInsensitive("Hello", "HELLO")) // trueconsole.log(caseInsensitive("foo", "bar")) // false
// Same reference optimizationconst str = "test"console.log(caseInsensitive(str, str)) // true (fast path)Example (Comparing numbers with tolerance)
import { Equivalence } from "effect"
const tolerance = Equivalence.make<number>((a, b) => Math.abs(a - b) < 0.0001)
console.log(tolerance(1.0, 1.001)) // falseconsole.log(tolerance(1.0, 1.00001)) // trueSee
strictEqualmapInput
Signature
declare const make: <A>(isEquivalent: (self: A, that: A) => boolean) => Equivalence<A>Since v2.0.0
makeReducer
Section titled “makeReducer”Creates a Reducer for combining Equivalence instances, useful for aggregating equivalences in collections.
When to use
Use when you need a reducer that combines equivalences.
Details
Returns a reducer that combines equivalences using combine. The identity
element for empty collections is an equivalence that always returns true.
The reducer uses combineAll for collections of equivalences and can be used
with fold operations.
Example (Creating a Reducer)
import { Equivalence } from "effect"
const reducer = Equivalence.makeReducer<number>()const equivalences = [Equivalence.strictEqual<number>(), Equivalence.make<number>((a, b) => Math.abs(a - b) < 1)]
const combined = reducer.combineAll(equivalences)// Combined equivalence requires both conditions to be trueconsole.log(combined(1, 1)) // true (strict equal)console.log(combined(1, 1.5)) // false (strict equal fails)See
combineCombine two equivalencescombineAllCombine multiple equivalencesReducerReducer type for collection operations
Signature
declare const makeReducer: <A>() => Reducer.Reducer<Equivalence<A>>Since v4.0.0
strictEqual
Section titled “strictEqual”Creates an equivalence relation that uses strict equality (===) to compare values.
When to use
Use when you need strict equality (===) as the comparison.
Details
Uses JavaScript’s strict equality operator (===). Primitives compare by
value. Objects compare by reference, so only the same object instance is
equivalent. Use this as a building block for more complex equivalences via
mapInput or combine.
Gotchas
NaN !== NaN, so NaN values are never considered equivalent.
Example (Comparing primitive types)
import { Equivalence } from "effect"
const strictEq = Equivalence.strictEqual<number>()
console.log(strictEq(1, 1)) // trueconsole.log(strictEq(1, 2)) // falseconsole.log(strictEq(NaN, NaN)) // false (NaN !== NaN)Example (Comparing objects by reference)
import { Equivalence } from "effect"
const obj = { value: 42 }const strictObjEq = Equivalence.strictEqual<typeof obj>()
console.log(strictObjEq(obj, obj)) // trueconsole.log(strictObjEq(obj, { value: 42 })) // false (different references)See
makeEqualfor structural equality
Signature
declare const strictEqual: <A>() => Equivalence<A>Since v4.0.0
instances
Section titled “instances”BigInt
Section titled “BigInt”Equivalence instance for bigints using strict equality (===).
When to use
Use when you need to supply bigint equality.
Example (Comparing bigints)
import { Equivalence } from "effect"
console.log(Equivalence.BigInt(1n, 1n)) // trueconsole.log(Equivalence.BigInt(1n, 2n)) // falseSignature
declare const BigInt: Equivalence<bigint>Since v4.0.0
Boolean
Section titled “Boolean”Equivalence instance for booleans using strict equality (===).
When to use
Use when you need to supply boolean equality.
Example (Comparing booleans)
import { Equivalence } from "effect"
console.log(Equivalence.Boolean(true, true)) // trueconsole.log(Equivalence.Boolean(true, false)) // falseSignature
declare const Boolean: Equivalence<boolean>Since v4.0.0
Equivalence instance for Date objects that compares their getTime() values using Equivalence.Number.
When to use
Use when you need an Equivalence for JavaScript date objects by their
millisecond timestamp.
Details
Different Date instances that represent the same millisecond timestamp are equivalent. Because Equivalence.Number
treats NaN as equal to NaN, two invalid Date values are also considered equivalent.
Example (Comparing Date values)
import { Equivalence } from "effect"
const d1 = new Date("2020-01-01T00:00:00.000Z")const d2 = new Date("2020-01-01T00:00:00.000Z")const d3 = new Date("2021-01-01T00:00:00.000Z")const invalidDate1 = new Date("foo")const invalidDate2 = new Date("bar")
console.log(Equivalence.Date(d1, d2)) // trueconsole.log(Equivalence.Date(d1, d3)) // falseconsole.log(Equivalence.Date(invalidDate1, invalidDate2)) // trueconsole.log(Equivalence.Date(invalidDate1, d1)) // falseExample (Comparing reference and value equality)
import { Equivalence } from "effect"
const d1 = new Date(0)const d2 = new Date(0)
console.log(d1 === d2) // false (different references)console.log(Equivalence.Date(d1, d2)) // true (same time value)See
Numberfor the numeric equivalence applied to eachDate#getTime()resultmapInputfor deriving an equivalence by mapping inputs before comparisonstrictEqualfor reference equality when two values must be the same object
Signature
declare const Date: Equivalence<Date>Since v2.0.0
Number
Section titled “Number”Equivalence instance for numbers.
When to use
Use when you need numeric equality that treats NaN as equal to itself.
Example (Comparing numbers)
import { Equivalence } from "effect"
console.log(Equivalence.Number(1, 1)) // trueconsole.log(Equivalence.Number(1, 2)) // falseconsole.log(Equivalence.Number(NaN, NaN)) // trueSignature
declare const Number: Equivalence<number>Since v4.0.0
String
Section titled “String”Equivalence instance for strings using strict equality (===).
When to use
Use when you need to supply case-sensitive string equality.
Example (Comparing strings)
import { Equivalence } from "effect"
console.log(Equivalence.String("hello", "hello")) // trueconsole.log(Equivalence.String("hello", "world")) // falseSignature
declare const String: Equivalence<string>Since v4.0.0
mapping
Section titled “mapping”mapInput
Section titled “mapInput”Transforms an equivalence relation by mapping the input values before comparison.
When to use
Use when you need an equivalence for one type by comparing a derived value.
Details
- Applies the transformation function to both values before comparing
- The transformation function should be pure and have no side effects
- The resulting equivalence compares the transformed values using the provided equivalence
- The result is also an equivalence that satisfies reflexive, symmetric, and transitive properties
- Useful for comparing by one property or normalizing values before comparison, such as case-insensitive strings
Example (Deriving equivalence from an object property)
import { Equivalence } from "effect"
interface User { id: number name: string email: string}
// Create equivalence based on user ID onlyconst userByIdEq = Equivalence.mapInput(Equivalence.strictEqual<number>(), (user: User) => user.id)
const user1 = { id: 1, name: "Alice", email: "alice@example.com" }const user2 = { id: 1, name: "Alice Smith", email: "alice.smith@example.com" }const user3 = { id: 2, name: "Bob", email: "bob@example.com" }
console.log(userByIdEq(user1, user2)) // true (same ID)console.log(userByIdEq(user1, user3)) // false (different ID)Example (Case-insensitive string equivalence)
import { Equivalence } from "effect"
const caseInsensitiveEq = Equivalence.mapInput(Equivalence.strictEqual<string>(), (s: string) => s.toLowerCase())
console.log(caseInsensitiveEq("Hello", "HELLO")) // trueconsole.log(caseInsensitiveEq("Hello", "World")) // falseSee
combineStruct
Signature
declare const mapInput: { <B, A>(f: (b: B) => A): (self: Equivalence<A>) => Equivalence<B> <A, B>(self: Equivalence<A>, f: (b: B) => A): Equivalence<B>}Since v2.0.0
type class
Section titled “type class”Equivalence (type alias)
Section titled “Equivalence (type alias)”Represents an equivalence relation over type A.
When to use
Use as a type annotation when you accept or return an equivalence function.
Details
- Returns
boolean:trueif values are equivalent,falseotherwise - Must satisfy reflexive, symmetric, and transitive properties
Example (Defining simple number equivalence)
import type { Equivalence } from "effect"
const numberEq: Equivalence.Equivalence<number> = (a, b) => a === b
console.log(numberEq(1, 1)) // trueconsole.log(numberEq(1, 2)) // falseExample (Defining custom object equivalence)
import type { Equivalence } from "effect"
interface Point { x: number y: number}
const pointEq: Equivalence.Equivalence<Point> = (a, b) => a.x === b.x && a.y === b.y
console.log(pointEq({ x: 1, y: 2 }, { x: 1, y: 2 })) // trueSee
makestrictEqual
Signature
type Equivalence<A> = (self: A, that: A) => booleanSince v2.0.0
type lambdas
Section titled “type lambdas”EquivalenceTypeLambda (interface)
Section titled “EquivalenceTypeLambda (interface)”Type lambda for Equivalence, used for higher-kinded type operations.
When to use
Use when you need to abstract over Equivalence in higher-kinded type code.
Details
- Enables
Equivalenceto work with the Effect type system’s HKT infrastructure - Used internally for type-level computations and generic abstractions
Example (Type-level usage)
import type { Equivalence, HKT } from "effect"
// Used internally for type-level computationstype NumberEquivalence = HKT.Kind<Equivalence.EquivalenceTypeLambda, never, never, never, number>// Equivalent to: Equivalence.Equivalence<number>See
EquivalenceTypeLambda
Signature
export interface EquivalenceTypeLambda extends TypeLambda { readonly type: Equivalence<this["Target"]>}Since v2.0.0
Array_
Section titled “Array_”Signature
declare const Array_: <A>(item: Equivalence<A>) => Equivalence<ReadonlyArray<A>>Since v4.0.0