The most common implementation strategies are writing classes and patching
existing prototypes. Implementation is implicit and does not require an
implements clause. TypeScript uses structural subtyping to determine
whether a value implements Eq.
Working with generic types requires additional consideration: in some cases,
a generic type implements Eqonly when one or more of its generic
parameters implement Eq; in these cases, we must require an Eq
implementation from the parameter(s). In other cases, there are no such
requirements.
Existing types can be patched to implement Eq. This strategy works well for
types that:
are built-in or imported from external modules.
do not provide access to their implementation.
have a single, specific behavior as an equivalence relation, or where the
programmer wishes to implement a default behavior.
Patching a type in TypeScript requires two steps:
an augmentation for a module or the global scope that patches the
type-level representation; and
a concrete implementation for [Eq.eq].
The concrete implementation logic is similar to writing a method body for a
class or object, and the same practices apply for requiring generic
parameters to implement Eq.
Example
Non-generic implementation
Consider a Book type that determines equality by comparing ISBNs:
Notice the extra syntax when implementing [Eq.eq]. We introduce a
method-scoped generic parameter T and require that it has an Eq
implementation by writing T extends Eq<T> (the name T is arbitrary).
Then, we require that this and that are Arr<T> where T extends Eq<T>.
This allows us to use ieq to implement our desired behavior.
Example
Generic implementation with multiple Eq requirements
Consider a Pair type that determines equality for two distinct values,
which requires that each value has a distinct implementation for Eq:
import { Eq, eq } from"@neotype/prelude/cmp.js";
classPair<outA, outB> { constructor(readonlyfst: A, readonlysnd: B) {}
The syntax is similar to the Arr implementation above. Notice there are now
two method-scoped generic parameters that are each required to implement
Eq.
Example
Non-generic augmentation
Consider a module augmentation for an externally defined Book type:
import { Eq } from"@neotype/prelude/cmp.js"; import { Book } from"path_to/book.js";
An interface that provides evidence of an equivalence relation.
Remarks
Properties
Implementors of
Eq
must implement an equality comparison that is:eq(x, x)
;eq(x, y)
implieseq(y, x)
; andeq(x, y)
andeq(y, z)
implieseq(x, z)
for all values
x
,y
, andz
.Implementing
Eq
Eq
requires an implementation for[Eq.eq]
.The most common implementation strategies are writing classes and patching existing prototypes. Implementation is implicit and does not require an
implements
clause. TypeScript uses structural subtyping to determine whether a value implementsEq
.Conditional implementation
Working with generic types requires additional consideration: in some cases, a generic type implements
Eq
only when one or more of its generic parameters implementEq
; in these cases, we must require anEq
implementation from the parameter(s). In other cases, there are no such requirements.Writing classes
Classes and objects can implement
Eq
. This strategy works best for types that:Additionally, classes can easily wrap existing types to provide a variety of
Eq
implementations. These "helper" classes are useful for types that:Eq
but can have alternative implementations.Patching existing prototypes
Existing types can be patched to implement
Eq
. This strategy works well for types that:Patching a type in TypeScript requires two steps:
[Eq.eq]
.The concrete implementation logic is similar to writing a method body for a class or object, and the same practices apply for requiring generic parameters to implement
Eq
.Example
Non-generic implementation
Consider a
Book
type that determines equality by comparing ISBNs:If desired, we can also require the same book format for two books to be considered equal:
Example
Generic implementation with no
Eq
requirementsConsider a type that determines equality by comparing the lengths of arrays:
Notice how
Len
is generic, but there are no special requirements for implementing[Eq.eq]
.Example
Generic implementation with an
Eq
requirementConsider a type that determines equality for arrays by comparing their elements lexicographically, which requires that the elements implement
Eq
:Notice the extra syntax when implementing
[Eq.eq]
. We introduce a method-scoped generic parameterT
and require that it has anEq
implementation by writingT extends Eq<T>
(the nameT
is arbitrary).Then, we require that
this
andthat
areArr<T>
whereT extends Eq<T>
. This allows us to useieq
to implement our desired behavior.Example
Generic implementation with multiple
Eq
requirementsConsider a
Pair
type that determines equality for two distinct values, which requires that each value has a distinct implementation forEq
:The syntax is similar to the
Arr
implementation above. Notice there are now two method-scoped generic parameters that are each required to implementEq
.Example
Non-generic augmentation
Consider a module augmentation for an externally defined
Book
type:Example
Generic augmentation
Consider a global augmentation for the
Array
prototype: