Within the realm of computer science, Generics are referred to as Parametric Polymorphism, showcasing their practical utility quite effectively (assuming one comprehends the concept).
But what exactly does this term signify?
To start with, let's examine the general meaning of "parametric." Essentially, it denotes the presence of parameters. It's universally acknowledged that parameters play a vital role. A function restricted to adding just 3 and 5 might be mundane. Conversely, a function capable of adding any pair of numbers while being parameterized based on those numbers is undeniably more versatile!
A type constructor (the computer science terminology for a generic type) generates a type from another type. The apt nomenclature 'type constructor' draws parallels to certain branches of mathematics where functions are deemed as 'constructors' or 'value constructors' since they produce new output values leveraging input values. Similarly, a type constructor operates at the type level; not at the value level.
Consider the Promise
type constructor—requiring a single argument like
number</code—to craft a <em>new type</em> denoted as "NumberPromise."</p>
<p>The rationale behind leveraging type constructors with parameters on the type level aligns with the logic favoring functions with parameters on the value level: facilitating the creation of novel entities from pre-existing ones.</p>
<p>Why is <em>parametric polymorphism</em>, more precisely where the type parameter remains entirely unspecified, considered beneficial? </p>
<p>Recall the broader notion of <em>polymorphism</em>: enabling code to execute operations across diverse types.</p>
<p>The prevalent form of polymorphism—<em>ad-hoc polymorphism</em—entails the code carrying out <em>dissimilar operations for each type</em> necessitating distinct, specific implementations tailored to every type under consideration. Object-oriented virtual method dispatch stands as an illustrative instance of ad-hoc polymorphism wherein each class contains its own distinctive method implementation.</p>
<p>(In practice, object-oriented programming typically amalgamates <em>subtype polymorphism</em> with ad-hoc polymorphism, supporting <em>differential code-reuse</em> where subtype implementations selectively alter inherited supertype behaviors via inheritance or prototype delegation.)</p>
<p>In contrast, parametric polymorphism harbors solely a <em>single operation</em> functional across <em>every conceivable type</em>. An elementary example involves a function computing the length of a singly-linked list, indifferent towards the element types; hence, being <em>parametric</em> regarding the element type, exemplified through:</p>
<pre class="lang-js"><code>function length<T>(list: List<T>) {
return list.rest === undefined ? 0 : 1 + length(list.rest);
}
This function abstains from engaging with the list elements directly, thereby offering generic functionality unrestricted by the element type.
Another case in point pertains to the identity function:
function id<T>(it: T) {
return it;
}
All these functions epitomize fully parametric behavior operating on any provided type argument devoid of concerns about the actual type per se.
Nevertheless, instances do exist where functions must possess some comprehension concerning the involved types. For example, a sort
operation mandate knowledge pertaining to sortable elements by establishing certain "bounds" around the type parameters, embodying bounded polymorphism:
interface Comparable<T> {
const enum Comparing {
LessThan = -1,
Equal,
GreaterThan
}
compareTo(other: T): Comparing
}
function sort<T extends Comparable<T>>(list: List<T>) {
// sorting algorithm implementation
}
Addressing your query about modeling utilizing union and intersection types, a fundamental differentiating factor lies in how parametric polymorphism treats the type as an argument stipulated by the user. Attempting to model a generic collection class through a union of all probable element types would entail cataloging every imaginable type ever conceived in advance—a clearly impractical endeavor. Anticipating every type feasible to populate a list, map, or array in meticulous detail proves infeasible.
Alternatively, contemplate function types: imposing a stringent requirement for functions to solely interact with a predefined exhaustive union of types expressly outlined by TypeScript developers appears overly restrictive, doesn't it?
An insightful post on Software Engineering delves into analogous facets albeit from a divergent perspective. While the discussion centers on C#, which exhibits greater rigidity relative to TypeScript due to lacking support for union or intersection types, you're bound to unearth pertinent insights:
- Generics vs common interface: Delving into the versatility of generics beyond collections and querying why restricting collections to a singular generalized type isn't preferable.