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
Eqmust 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
EqEqrequires 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
implementsclause. 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
Eqonly when one or more of its generic parameters implementEq; in these cases, we must require anEqimplementation 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
Eqimplementations. These "helper" classes are useful for types that:Eqbut 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
Booktype 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
EqrequirementsConsider a type that determines equality by comparing the lengths of arrays:
Notice how
Lenis generic, but there are no special requirements for implementing[Eq.eq].Example
Generic implementation with an
EqrequirementConsider 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 parameterTand require that it has anEqimplementation by writingT extends Eq<T>(the nameTis arbitrary).Then, we require that
thisandthatareArr<T>whereT extends Eq<T>. This allows us to useieqto implement our desired behavior.Example
Generic implementation with multiple
EqrequirementsConsider a
Pairtype that determines equality for two distinct values, which requires that each value has a distinct implementation forEq:The syntax is similar to the
Arrimplementation 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
Booktype:Example
Generic augmentation
Consider a global augmentation for the
Arrayprototype: