Equality — Composable Equality Checks
Checking whether two values are equal is one of the most common requirements in software development. We do it to deduplicate arrays of items, detect changes in a user profile before saving, or confirm if an asset is already in a local collection.
For primitive types like strings, numbers, or booleans, JavaScript’s built-in === operator works
perfectly. However, for structured objects, arrays, and dates, === fails to capture our intent:
Two objects holding identical fields are not equal in standard JavaScript because they occupy
different locations in memory. Similarly, two Date objects representing the exact same millisecond
will evaluate to false when compared with ===.
To solve this, we often resort to serializing objects with
JSON.stringify(a) === JSON.stringify(b). But serialization is slow, fragile (it fails if keys are
written in a different order), and structurally incapable of handling functions, dates, or custom
maps. Alternatively, we write one-off comparison functions by hand, but they do not compose — you
cannot combine two checkers into one, or lift a string checker to compare record fields without
writing new boilerplate from scratch.
Equality<A> solves this mismatch. It represents a first-class, pure description of equivalence:
Any binary function matching this signature is a valid Equality checker. By treating equality as a
composable value, we can construct, name, and combine equivalence checkers point-free.
Built-In Equivalence Instances
Section titled “Built-In Equivalence Instances”The library provides optimized, built-in instances for common primitive types:
Deep Array Equivalence
Section titled “Deep Array Equivalence”To compare arrays, we pass the element-level equivalence checker to Equality.array:
Comparing by Fields: by
Section titled “Comparing by Fields: by”Equality.by adapts an equality checker designed for type A to operate on a richer type B by
extracting the target field to compare:
Combining Checks: and
Section titled “Combining Checks: and”When we need to assert that two complex objects are equivalent across multiple different fields, we
combine their respective checkers using and:
Practical Application: Custom Deduplication
Section titled “Practical Application: Custom Deduplication”Arr.uniqWith accepts any Equality<A> instance to remove duplicate values from a collection,
allowing you to deduplicate complex objects structurally:
Problems it solves
Section titled “Problems it solves”- UI change detection without redundant re-renders: In UI component memoization (such as React
memoor custom state selectors), comparing object props with===triggers false-positive renders because fresh object references are created on each pass.Equality.structandEquality.arraycompare fields structurally, preventing unnecessary re-renders. - Deduplicating event streams by compound keys: When filtering activity feeds or telemetry
queues, deduplicating items based on multiple fields (such as
userId+action+entityId) requires explicit equality definitions.Equality.structandEquality.andcompose field comparators for use with collection helpers likeArr.uniqWith. - Projected property and normalized comparisons (
Equality.by): Comparing domain models by specific projected properties (such as comparing users by email address case-insensitively, or orders by currency value) without writing repetitive equality callbacks. - Accurate Date and value-object comparisons: Comparing JavaScript
Dateinstances with===tests reference identity rather than chronological time, causing identical dates to evaluate as unequal.Equality.dateprovides accurate timestamp comparisons across domain models.