Semigroup requires an implementation for [Semigroup.cmb].
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 Semigroup.
Working with generic types requires additional consideration: in some cases,
a generic type implements Semigrouponly when one or more of its
generic parameters implement Semigroup; in these cases, we must require a
Semigroup implementation from the parameter(s). In other cases, there are
no such requirements.
Classes and objects can implement Semigroup. This strategy works best for
types that:
are already modeled using classes or objects.
provide direct access to their implementation.
have a single, specific behavior as a semigroup.
Additionally, classes can easily wrap existing types to provide a variety of
Semigroup implementations. These "helper" classes are useful for types
that:
have more than one behavior as a semigroup, or already have a default
implementation for Semigroup but can have alternative implementations.
do not provide access to their implementation, and where patching the
implementation is undesireable.
Existing types can be patched to implement Semigroup. 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 a semigroup, 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 [Semigroup.cmb].
The concrete implementation logic is similar to writing a method body for a
class or object, and the same practices apply when requiring generic type
parameters to implement Semigroup.
Example
Non-generic implementation
Consider a type that combines strings using concatenation:
Notice the extra syntax when implementing [Semigroup.cmb]. We introduce
a method-scoped generic parameter T and require that it has a Semigroup
implementation by writing T extends Semigroup<T> (the name T is
arbitrary).
Then, we require that this and that are Async<T> where T extends Semigroup<T>. This allows us to use cmb to implement our desired behavior.
Example
Generic implementation with multiple Semigroup requirements
Consider a type that combines two values pairwise, which requires that each
value implement Semigroup:
The syntax is similar to the Async implementation above. Notice there are
now two method-scoped generic parameters that are each required to implement
Semigroup.
Example
Non-generic augmentation
Consider a global augmentation for the String prototype:
An interface that provides evidence of a semigroup.
Remarks
Properties
Implementors of
Semigroupmust implement an operation that satisfies the associative property, such that:cmb(x, cmb(y, z))is equivalent tocmb(cmb(x, y), z)for all values
x,y, andz.Implementing
SemigroupSemigrouprequires an implementation for[Semigroup.cmb].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 implementsSemigroup.Conditional implementation
Working with generic types requires additional consideration: in some cases, a generic type implements
Semigrouponly when one or more of its generic parameters implementSemigroup; in these cases, we must require aSemigroupimplementation from the parameter(s). In other cases, there are no such requirements.Writing classes
Classes and objects can implement
Semigroup. This strategy works best for types that:Additionally, classes can easily wrap existing types to provide a variety of
Semigroupimplementations. These "helper" classes are useful for types that:Semigroupbut can have alternative implementations.Patching existing prototypes
Existing types can be patched to implement
Semigroup. This strategy works well for types that:Patching a type in TypeScript requires two steps:
[Semigroup.cmb].The concrete implementation logic is similar to writing a method body for a class or object, and the same practices apply when requiring generic type parameters to implement
Semigroup.Example
Non-generic implementation
Consider a type that combines strings using concatenation:
Example
Generic implementation with no
SemigrouprequirementsConsider a type that combines arrays using concatenation:
Notice how
Concatis generic, but there are no special requirements for implementing[Semigroup.cmb].Example
Generic implementation with a
SemigrouprequirementConsider a type that combines promises by combining their results, which requires that the results also implement
Semigroup:Notice the extra syntax when implementing
[Semigroup.cmb]. We introduce a method-scoped generic parameterTand require that it has aSemigroupimplementation by writingT extends Semigroup<T>(the nameTis arbitrary).Then, we require that
thisandthatareAsync<T>whereT extends Semigroup<T>. This allows us to usecmbto implement our desired behavior.Example
Generic implementation with multiple
SemigrouprequirementsConsider a type that combines two values pairwise, which requires that each value implement
Semigroup:The syntax is similar to the
Asyncimplementation above. Notice there are now two method-scoped generic parameters that are each required to implementSemigroup.Example
Non-generic augmentation
Consider a global augmentation for the
Stringprototype:Example
Generic augmentation
Consider a module augmentation for an externally defined
Pairtype: