Skip to content

Equality

In v3, Equal.equals used reference equality for plain objects and arrays. Structural comparison was only available inside a structuralRegion, which temporarily enabled deep comparison. Outside a structural region, two distinct objects with identical contents were not considered equal:

// v3
import { Equal } from "effect"
Equal.equals({ a: 1 }, { a: 1 }) // false — reference equality
Equal.equals([1, 2], [1, 2]) // false — reference equality

In v4, Equal.equals uses structural equality by default. Plain objects, arrays, Maps, Sets, Dates, and RegExps are compared by value without opting in:

// v4
import { Equal } from "effect"
Equal.equals({ a: 1 }, { a: 1 }) // true
Equal.equals([1, [2, 3]], [1, [2, 3]]) // true
Equal.equals(new Map([["a", 1]]), new Map([["a", 1]])) // true
Equal.equals(new Set([1, 2]), new Set([1, 2])) // true

Objects that implement the Equal interface continue to use their custom equality logic, same as v3.

If you need reference equality for a specific object, v4 provides Equal.byReference and Equal.byReferenceUnsafe:

import { Equal } from "effect"
const obj = Equal.byReference({ a: 1 })
Equal.equals(obj, { a: 1 }) // false — reference equality
  • byReference(obj) — creates a Proxy that uses reference equality, leaving the original object unchanged.
  • byReferenceUnsafe(obj) — marks the object itself for reference equality without creating a proxy. More performant but permanently changes how the object is compared.

In v3, Equal.equals(NaN, NaN) returned false (following IEEE 754). In v4, NaN is considered equal to NaN:

Equal.equals(NaN, NaN) // v3: false, v4: true

The function that wraps equals as an Equivalence has been renamed:

// v3
Equal.equivalence<number>()
// v4
Equal.asEquivalence<number>()