Equivalence
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.
Combinators
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
Signature
declare function Record<A>(value: Equivalence<A>): Equivalence<Record<PropertyKey, A>>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'
stringRecordEq(record1, record2) // => truestringRecordEq(record1, record3) // => falsestringRecordEq(record1, record4) // => falseExample
(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 }
numberRecordEq(scores1, scores2) // => truenumberRecordEq(scores1, scores3) // => falseCreates 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.
See
Signature
declare function Struct<R extends Record<string, Equivalence<any>>>(fields: R): Equivalence<{ [K in string | number | symbol]: [R[K]] extends [Equivalence<A>] ? A : never }>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" }
personEq(person1, person2) // => truepersonEq(person1, person3) // => falseExample
(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" }nameAgeEq(obj1, obj2) // => trueCreates 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.
Signature
declare function Tuple<Elements extends readonly Array<Equivalence<any>>>(elements: Elements): Equivalence<{ [I in string | number | symbol]: [Elements[I]] extends [Equivalence<A>] ? A : never }>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
stringTupleEq(tuple1, tuple2) // => truestringTupleEq(tuple1, tuple3) // => falseExample
(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])
customTupleEq(["Hello", "World", "Test"], ["HELLO", "WORLD", "TEST"]) // => trueCombining
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.
See
Signature
declare const combine: { <A>(that: Equivalence<A>): (self: Equivalence<A>) => Equivalence<A>; <A>(self: Equivalence<A>, that: Equivalence<A>): Equivalence<A>;}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 }
personEquivalence(person1, person2) // => truepersonEquivalence(person1, person3) // => falsecombineAll
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.
See
Signature
declare function combineAll<A>(collection: Iterable<Equivalence<A>>): Equivalence<A>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 }
point3DEq(point1, point2) // => truepoint3DEq(point1, point3) // => falseExample
(Handling empty collections)
import { Equivalence } from "effect"
// Empty collection always returns trueconst alwaysEq = Equivalence.combineAll([])alwaysEq("anything", "else") // => trueConstructors
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.
See
Signature
declare function make<A>(isEquivalent: (self: A, that: A) => boolean): Equivalence<A>Example
(Case-insensitive string equivalence)
import { Equivalence } from "effect"
const caseInsensitive = Equivalence.make<string>((a, b) => a.toLowerCase() === b.toLowerCase())
caseInsensitive("Hello", "HELLO") // => truecaseInsensitive("foo", "bar") // => false
// Same reference optimizationconst str = "test"caseInsensitive(str, str) // => trueExample
(Comparing numbers with tolerance)
import { Equivalence } from "effect"
const tolerance = Equivalence.make<number>((a, b) => Math.abs(a - b) < 0.0001)
tolerance(1.0, 1.001) // => falsetolerance(1.0, 1.00001) // => truemakeReducer
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.
See
- combine Combine two equivalences
- combineAll Combine multiple equivalences
- Reducer Reducer type for collection operations
Signature
declare function makeReducer<A>(): Reducer<Equivalence<A>>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 truecombined(1, 1) // => truecombined(1, 1.5) // => falsestrictEqual
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.
See
- make
Equalfor structural equality
Signature
declare const strictEqual: <A>() => Equivalence<A>Example
(Comparing primitive types)
import { Equivalence } from "effect"
const strictEq = Equivalence.strictEqual<number>()
strictEq(1, 1) // => truestrictEq(1, 2) // => falsestrictEq(NaN, NaN) // => falseExample
(Comparing objects by reference)
import { Equivalence } from "effect"
const obj = { value: 42 }const strictObjEq = Equivalence.strictEqual<typeof obj>()
strictObjEq(obj, obj) // => truestrictObjEq(obj, { value: 42 }) // => falseInstances
Equivalence instance for bigints using strict equality (===).
When to use
Use when you need to supply bigint equality.
Signature
declare const BigInt: Equivalence<bigint>Example
(Comparing bigints)
import { Equivalence } from "effect"
Equivalence.BigInt(1n, 1n) // => trueEquivalence.BigInt(1n, 2n) // => falseEquivalence instance for booleans using strict equality (===).
When to use
Use when you need to supply boolean equality.
Signature
declare const Boolean: Equivalence<boolean>Example
(Comparing booleans)
import { Equivalence } from "effect"
Equivalence.Boolean(true, true) // => trueEquivalence.Boolean(true, false) // => falseEquivalence 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.
See
- Number for the numeric equivalence applied to each
Date#getTime()result - mapInput for deriving an equivalence by mapping inputs before comparison
- strictEqual for reference equality when two values must be the same object
Signature
declare const Date: Equivalence<Date>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")
Equivalence.Date(d1, d2) // => trueEquivalence.Date(d1, d3) // => falseEquivalence.Date(invalidDate1, invalidDate2) // => trueEquivalence.Date(invalidDate1, d1) // => falseExample
(Comparing reference and value equality)
import { Equivalence } from "effect"
const d1 = new Date(0)const d2 = new Date(0)
d1 === d2 // => falseEquivalence.Date(d1, d2) // => trueEquivalence instance for numbers.
When to use
Use when you need numeric equality that treats NaN as equal to itself.
Signature
declare const Number: Equivalence<number>Example
(Comparing numbers)
import { Equivalence } from "effect"
Equivalence.Number(1, 1) // => trueEquivalence.Number(1, 2) // => falseEquivalence.Number(NaN, NaN) // => trueEquivalence instance for strings using strict equality (===).
When to use
Use when you need to supply case-sensitive string equality.
Signature
declare const String: Equivalence<string>Example
(Comparing strings)
import { Equivalence } from "effect"
Equivalence.String("hello", "hello") // => trueEquivalence.String("hello", "world") // => falseMapping
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
See
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>;}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" }
userByIdEq(user1, user2) // => trueuserByIdEq(user1, user3) // => falseExample
(Case-insensitive string equivalence)
import { Equivalence } from "effect"
const caseInsensitiveEq = Equivalence.mapInput( Equivalence.strictEqual<string>(), (s: string) => s.toLowerCase())
caseInsensitiveEq("Hello", "HELLO") // => truecaseInsensitiveEq("Hello", "World") // => falseModels
Equivalence type
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
See
Signature
type Equivalence<in A> = (self: A, that: A) => booleanExample
(Defining simple number equivalence)
import type { Equivalence } from "effect"
const numberEq: Equivalence.Equivalence<number> = (a, b) => a === b
numberEq(1, 1) // => truenumberEq(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
pointEq({ x: 1, y: 2 }, { x: 1, y: 2 }) // => trueOther
Utility Types
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
See
Signature
interface EquivalenceTypeLambda extends TypeLambda { readonly type: Equivalence<unknown>;}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>