Equal.ts
Equal.ts overview
Section titled “Equal.ts overview”Compares values with Effect’s structural equality rules.
equals compares primitives, arrays, plain objects, maps, sets, dates,
regular expressions, and values that implement the Equal interface. This
module also defines the equality symbol, guards, adapters, map and set
comparison builders, and helpers for marking objects that should compare only
by reference.
Since v2.0.0
Exports Grouped by Category
Section titled “Exports Grouped by Category”equality
Section titled “equality”byReference
Section titled “byReference”Creates a proxy that uses reference equality instead of structural equality.
When to use
Use when you need to compare a plain object or array by identity without mutating the original value.
Details
- Returns a
Proxywrappingobj. The proxy reads through to the original, so property access is unchanged. - The proxy is registered in an internal WeakSet;
equalsreturnsfalsefor any pair where at least one operand is in that set (unless they are the same reference). - Each call creates a new proxy, so
byReference(x) !== byReference(x). - Does not mutate the original object (unlike
byReferenceUnsafe).
Example (Opting out of structural equality)
import { Equal } from "effect"
const a = { x: 1 }const b = { x: 1 }
console.log(Equal.equals(a, b)) // true (structural)
const aRef = Equal.byReference(a)console.log(Equal.equals(aRef, b)) // false (reference)console.log(Equal.equals(aRef, aRef)) // true (same reference)console.log(aRef.x) // 1 (proxy reads through)See
byReferenceUnsafe— same effect without a proxy (mutates the original)equals— the comparison function affected by this opt-out
Signature
declare const byReference: <T extends object>(obj: T) => TSince v4.0.0
equals
Section titled “equals”Checks whether two values are deeply structurally equal.
When to use
Use when you need Effect’s default structural equality check.
Details
Returns a boolean and never throws. Primitives are compared by value, and
NaN equals NaN. Objects implementing Equal delegate to their
[Equal.symbol] method; if only one operand implements Equal, the result
is false.
Dates compare by ISO string, RegExps compare by string representation,
arrays compare element-by-element, Maps and Sets compare entries
order-independently, and plain objects compare enumerable keys recursively.
Functions without an Equal implementation compare by reference. Circular
references are handled when both structures are circular at the same depth.
Hash values are checked first as a fast-path rejection. The function also supports dual data-last usage: call it with one argument to get a curried predicate.
Gotchas
- Results are cached per object pair in a WeakMap. Objects must not be mutated after their first comparison.
- Map and Set comparisons are O(n²) in size.
Example (Comparing values)
import { Equal } from "effect"
// Primitivesconsole.log(Equal.equals(1, 1)) // trueconsole.log(Equal.equals(NaN, NaN)) // trueconsole.log(Equal.equals("a", "b")) // false
// Objects and arraysconsole.log(Equal.equals({ a: 1, b: 2 }, { a: 1, b: 2 })) // trueconsole.log(Equal.equals([1, [2, 3]], [1, [2, 3]])) // true
// Datesconsole.log(Equal.equals(new Date("2024-01-01"), new Date("2024-01-01"))) // true
// Maps (order-independent)const m1 = new Map([ ["a", 1], ["b", 2]])const m2 = new Map([ ["b", 2], ["a", 1]])console.log(Equal.equals(m1, m2)) // true
// Curried formconst is5 = Equal.equals(5)console.log(is5(5)) // trueconsole.log(is5(3)) // falseSee
Equal— the interface for custom equalityisEqual— check whether a value implementsEqualasEquivalence— wrapequalsas anEquivalence
Signature
declare const equals: { <B>(that: B): <A>(self: A) => boolean; <A, B>(self: A, that: B): boolean }Since v2.0.0
guards
Section titled “guards”isEqual
Section titled “isEqual”Checks whether a value implements the Equal interface.
When to use
Use when you need generic utility code to distinguish Equal implementors
from plain values before calling [Equal.symbol] directly.
Details
- Pure function, no side effects.
- Returns
trueif and only ifuhas a property keyed bysymbol. - Acts as a TypeScript type guard, narrowing the input to
Equal.
Example (Checking Equal values)
import { Equal, Hash } from "effect"
class Token implements Equal.Equal { constructor(readonly value: string) {} [Equal.symbol](that: Equal.Equal): boolean { return that instanceof Token && this.value === that.value } [Hash.symbol](): number { return Hash.string(this.value) }}
console.log(Equal.isEqual(new Token("abc"))) // trueconsole.log(Equal.isEqual({ x: 1 })) // falseconsole.log(Equal.isEqual(42)) // falseSee
Equal— the interface being checkedsymbol— the property key that signalsEqualsupport
Signature
declare const isEqual: (u: unknown) => u is EqualSince v2.0.0
instances
Section titled “instances”asEquivalence
Section titled “asEquivalence”Wraps equals as an Equivalence<A>.
When to use
Use when you want to pass Equal.equals to APIs that require an
Equivalence.
Details
- Returns a function
(a: A, b: A) => booleanthat delegates toequals. - Pure; allocates a thin wrapper on each call.
Example (Deduplicating with Equal semantics)
import { Array, Equal } from "effect"
const eq = Equal.asEquivalence<number>()const result = Array.dedupeWith([1, 2, 2, 3, 1], eq)console.log(result) // [1, 2, 3]See
equals— the underlying comparison function
Signature
declare const asEquivalence: <A>() => Equivalence<A>Since v4.0.0
models
Section titled “models”Equal (interface)
Section titled “Equal (interface)”The interface for types that define their own equality logic.
When to use
Use when you need value-based equality for a class (e.g. domain IDs, coordinates, money values).
- When your type will be stored in
HashMaporHashSet. - When the default structural comparison is too broad or too narrow for your type.
Details
Any object that implements both [Equal.symbol] (equality) and
[Hash.symbol] (hashing) is recognized by equals and by hash-based
collections such as HashMap and HashSet.
- Extends
Hash.Hash, so implementors must also provide[Hash.symbol]. - The hash contract: if
a[Equal.symbol](b)returnstrue, thenHash.hash(a)must equalHash.hash(b). equalsdelegates to this method when both operands implement it. If only one operand implementsEqual, they are considered unequal.
Example (Comparing coordinates by value)
import { Equal, Hash } from "effect"
class Coordinate implements Equal.Equal { constructor( readonly x: number, readonly y: number ) {}
[Equal.symbol](that: Equal.Equal): boolean { return that instanceof Coordinate && this.x === that.x && this.y === that.y }
[Hash.symbol](): number { return Hash.string(`${this.x},${this.y}`) }}
console.log(Equal.equals(new Coordinate(1, 2), new Coordinate(1, 2))) // trueconsole.log(Equal.equals(new Coordinate(1, 2), new Coordinate(3, 4))) // falseSee
symbol— the property key used by the equality methodequals— the main comparison functionisEqual— type guard forEqualimplementors
Signature
export interface Equal extends Hash.Hash { [symbol](that: Equal): boolean}Since v2.0.0
symbols
Section titled “symbols”symbol
Section titled “symbol”Defines the unique string identifier for the Equal interface.
When to use
Use when you implement custom equality and need the computed property key for the equality method.
Details
This is a pure constant with no allocation or side effects.
Example (Implementing Equal on a class)
import { Equal, Hash } from "effect"
class UserId implements Equal.Equal { constructor(readonly id: string) {}
[Equal.symbol](that: Equal.Equal): boolean { return that instanceof UserId && this.id === that.id }
[Hash.symbol](): number { return Hash.string(this.id) }}See
Equal— the interface that uses this symbolisEqual— type guard forEqualimplementors
Signature
declare const symbol: "~effect/interfaces/Equal"Since v2.0.0
unsafe
Section titled “unsafe”byReferenceUnsafe
Section titled “byReferenceUnsafe”Marks an object permanently to use reference equality, without creating a proxy.
When to use
Use when you need reference equality without proxy allocation and accept permanently marking the original object for reference-only equality.
Details
- Adds
objto an internal WeakSet. From that point on,equalstreats it as reference-only. - Returns the same object (not a copy or proxy), so
byReferenceUnsafe(x) === x. - Does not affect the object’s prototype, properties, or behavior beyond equality checks.
Gotchas
The marking is irreversible for the lifetime of the object.
Example (Marking an object for reference equality)
import { Equal } from "effect"
const obj1 = { a: 1, b: 2 }const obj2 = { a: 1, b: 2 }
Equal.byReferenceUnsafe(obj1)
console.log(Equal.equals(obj1, obj2)) // false (reference)console.log(Equal.equals(obj1, obj1)) // true (same reference)console.log(obj1 === Equal.byReferenceUnsafe(obj1)) // true (same object)See
byReference— safer alternative that creates a proxyequals— the comparison function affected by this opt-out
Signature
declare const byReferenceUnsafe: <T extends object>(obj: T) => TSince v4.0.0