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
Semigroup
must 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
Semigroup
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 implementsSemigroup
.Conditional implementation
Working with generic types requires additional consideration: in some cases, a generic type implements
Semigroup
only when one or more of its generic parameters implementSemigroup
; in these cases, we must require aSemigroup
implementation 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
Semigroup
implementations. These "helper" classes are useful for types that:Semigroup
but 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
Semigroup
requirementsConsider a type that combines arrays using concatenation:
Notice how
Concat
is generic, but there are no special requirements for implementing[Semigroup.cmb]
.Example
Generic implementation with a
Semigroup
requirementConsider 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 parameterT
and require that it has aSemigroup
implementation by writingT extends Semigroup<T>
(the nameT
is arbitrary).Then, we require that
this
andthat
areAsync<T>
whereT extends Semigroup<T>
. This allows us to usecmb
to implement our desired behavior.Example
Generic implementation with multiple
Semigroup
requirementsConsider 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 implementSemigroup
.Example
Non-generic augmentation
Consider a global augmentation for the
String
prototype:Example
Generic augmentation
Consider a module augmentation for an externally defined
Pair
type: