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 equalityIn 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])) // trueObjects 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 equalitybyReference(obj)— creates aProxythat 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: trueThe function that wraps equals as an Equivalence has been renamed:
// v3
Equal.equivalence<number>()
// v4
Equal.asEquivalence<number>()