Skip to content

Equivalence.ts

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



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 0 with index 0
  • Returns true only 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])) // true
console.log(numberArrayEq([1, 2, 3], [1, 2, 4])) // false
console.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"])) // true
console.log(stringArrayEq(["Hello"], ["Hi"])) // false
console.log(stringArrayEq([], [])) // true (empty arrays)

See

  • Tuple
  • Record

Signature

declare const Array: <A>(item: Equivalence<A>) => Equivalence<ReadonlyArray<A>>

Source

Since v4.0.0

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)) // true
console.log(stringRecordEq(record1, record3)) // false
console.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)) // true
console.log(numberRecordEq(scores1, scores3)) // false

Signature

declare const Record: <A>(value: Equivalence<A>) => Equivalence<Record<PropertyKey, A>>

Source

Since v4.0.0

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 properties
const obj1 = { name: "Alice", age: 30, extra: "ignored" }
const obj2 = { name: "Alice", age: 30, extra: "different" }
console.log(nameAgeEq(obj1, obj2)) // true

See

  • Record
  • mapInput
  • combine

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 }>

Source

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 const
const tuple2 = ["hello", "world", "test"] as const
const tuple3 = ["hello", "world", "different"] as const
console.log(stringTupleEq(tuple1, tuple2)) // true
console.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"])) // true

Signature

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 }>

Source

Since v4.0.0

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)) // true
console.log(personEquivalence(person1, person3)) // false (different age)

See

  • combineAll
  • mapInput

Signature

declare const combine: {
<A>(that: Equivalence<A>): (self: Equivalence<A>) => Equivalence<A>
<A>(self: Equivalence<A>, that: Equivalence<A>): Equivalence<A>
}

Source

Since v2.0.0

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)) // true
console.log(point3DEq(point1, point3)) // false (different z)

Example (Handling empty collections)

import { Equivalence } from "effect"
// Empty collection always returns true
const alwaysEq = Equivalence.combineAll([])
console.log(alwaysEq("anything", "else")) // true

See

  • combine
  • mapInput

Signature

declare const combineAll: <A>(collection: Iterable<Equivalence<A>>) => Equivalence<A>

Source

Since v2.0.0

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")) // true
console.log(caseInsensitive("foo", "bar")) // false
// Same reference optimization
const 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)) // false
console.log(tolerance(1.0, 1.00001)) // true

See

  • strictEqual
  • mapInput

Signature

declare const make: <A>(isEquivalent: (self: A, that: A) => boolean) => Equivalence<A>

Source

Since v2.0.0

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 true
console.log(combined(1, 1)) // true (strict equal)
console.log(combined(1, 1.5)) // false (strict equal fails)

See

  • combine Combine two equivalences
  • combineAll Combine multiple equivalences
  • Reducer Reducer type for collection operations

Signature

declare const makeReducer: <A>() => Reducer.Reducer<Equivalence<A>>

Source

Since v4.0.0

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)) // true
console.log(strictEq(1, 2)) // false
console.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)) // true
console.log(strictObjEq(obj, { value: 42 })) // false (different references)

See

  • make
  • Equal for structural equality

Signature

declare const strictEqual: <A>() => Equivalence<A>

Source

Since v4.0.0

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)) // true
console.log(Equivalence.BigInt(1n, 2n)) // false

Signature

declare const BigInt: Equivalence<bigint>

Source

Since v4.0.0

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)) // true
console.log(Equivalence.Boolean(true, false)) // false

Signature

declare const Boolean: Equivalence<boolean>

Source

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)) // true
console.log(Equivalence.Date(d1, d3)) // false
console.log(Equivalence.Date(invalidDate1, invalidDate2)) // true
console.log(Equivalence.Date(invalidDate1, d1)) // false

Example (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

  • 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>

Source

Since v2.0.0

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)) // true
console.log(Equivalence.Number(1, 2)) // false
console.log(Equivalence.Number(NaN, NaN)) // true

Signature

declare const Number: Equivalence<number>

Source

Since v4.0.0

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")) // true
console.log(Equivalence.String("hello", "world")) // false

Signature

declare const String: Equivalence<string>

Source

Since v4.0.0

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 only
const 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")) // true
console.log(caseInsensitiveEq("Hello", "World")) // false

See

  • combine
  • Struct

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>
}

Source

Since v2.0.0

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: true if values are equivalent, false otherwise
  • 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)) // true
console.log(numberEq(1, 2)) // false

Example (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 })) // true

See

  • make
  • strictEqual

Signature

type Equivalence<A> = (self: A, that: A) => boolean

Source

Since v2.0.0

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 Equivalence to 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 computations
type NumberEquivalence = HKT.Kind<Equivalence.EquivalenceTypeLambda, never, never, never, number>
// Equivalent to: Equivalence.Equivalence<number>

See

  • Equivalence
  • TypeLambda

Signature

export interface EquivalenceTypeLambda extends TypeLambda {
readonly type: Equivalence<this["Target"]>
}

Source

Since v2.0.0

Signature

declare const Array_: <A>(item: Equivalence<A>) => Equivalence<ReadonlyArray<A>>

Source

Since v4.0.0