For the purpose of this explanation, I will define "specific" as the opposite of "generic". While some may use "concrete" to convey this idea, there is a risk of confusion with the term "abstract", which is unrelated to abstract classes.
Aside from generic functions, TypeScript only supports generic types, not generic values. To declare a generic type, the type parameter is placed in angle brackets after the type name:
type GenericType<T> = {x: T};
You can define a generic type like Foo<T>
, but any actual value of that type must be specific and include a specified specific type for T
:
declare const badValue1: GenericType; // error, needs 1 type argument
declare const badValue2: GenericType<T>; // error, 'T' not found
declare const goodValue: GenericType<string>; // valid
It's important to note that GenericType<string>
now represents a specific type equivalent to {x: string}
. Specifying generic parameters results in a specific type output.
Unlike generic types, values of generic functions are themselves generic. They encompass a variety of specific function types. In a generic function type, the type parameter comes before the function's parameter list within angle brackets:
type GenericFunction = <T>(x: T, y: T) => void;
The generic function's type itself is not generic; no type parameter appears in the GenericFunction
declaration above. Therefore, you only specify the type parameter when actually calling the function:
declare const badFunc: GenericFunction<string>; // error, not a generic function
declare const goodFunc: GenericFunction; // valid
const ret = goodFunc<string>("okay", "fine"); // type parameter set as string
const ret2 = goodFunc("okay", "fine"); // type parameter inferred as string
Simply put, the distinction between these two:
type IdGenericType<T> = (x: T) => T;
type IdGenericFunc = <T>(x: T) => T;
lies in the fact that the first is a generic type that resolves to a specific function upon specification, while the second is a specific type representing a generic function. Although related, these types are not interchangeable. You can assign a value of type IdGenericFunc
to any variable of type IdGenericType<XXX>
, using any specific type XXX
:
let id: IdGenericFunc = x => x;
let idString: IdGenericType<string> = id; // valid
But the reverse does not hold true:
const otherId: IdGenericFunc = idString; // error! Incompatible types
This discrepancy makes sense since an IdGenericType<string>
is known to accept and return a string
only:
idString = x => x + "!"; // valid
Therefore, assuming an IdGenericType<string>
is identical to a valid IdGenericFunc
would be inaccurate. The relationship between IdGenericType<T>
and IdGenericFunc
can be likened to intersecting all instances of IdGenericType<T>
across all possible T
values:
// type IdGenericFunc = forall T. IdGenericType<T>; // Syntax invalid
Although TypeScript lacks a direct way to represent this concept (borrowed syntax from Haskell), the fundamental distinctions remain.
To delve deeper into generic values in TypeScript, refer to the GitHub issue at microsoft/TypeScript#17574.
Code Link Here