Generics
Now we come to a very interesting aspect of TypeScript namely generic Types. They are also known as parameterized types or templated Types.
Let us consider a simple example:
function echo(aValue:number):number{
return aValue;
}
This function takes a number and returns a number.
Now if we need a function that takes a string and returns a string what do we do? We write another function:
function echo(aValue:string):string{
return aValue;
}
As you can see this is just repeated code!
Instead we can parametrize or template the function. We write the function echo
as a parametrized function that depends on a type parameter(which we call it T
).
So here is the function rewritten to take the type parameter.
function echo<T>(aValue:T):T{
return aValue;
}
Notice all we have done is to replace all occurrences of the type number
with the generic type parameter T
. Also, we have changed the function from echo
to echo<T>
to indicate that it is a parameterized function. Notice how the type paramters are placed within the <>
brackets and the function paramters are placed within the ()
brackets.
Now when we want to use the function we do so as follows:
let echoInt = echo<int>(2);
let echoInt = echo(2);
let echoString = echo("Hello");
We can specify the type parameter for the echo function explicitly or let the compiler infer it as shown above. Since this code is compiled to JavaScript essentially the type parameters are removed and replaced with javascript object references. (In languages like c++ and C# generics will create appropriate instances of paramtrized functions and classes with the relevant types inserted which will result not only in type safety but also performance improvements.)
However before all the type information is erased, there is a lot of checking that the TypeScript compiler can do for you. And we will review this and other features in this chapter.
Before we leave this section let us also look at the function signature of the parametrized function:
let functionEcho: <T>(T)=>T;
functionEcho=echo;
//This will error
//functionEcho=echoInt;
This just denotes that the variable functionEcho
can only be assigned a function that has the right type signature i.e, it depends on one type parameter, takes one function parameter of type T and returns one parameter of type T.
Parametrized classes and interfaces
Now we can use the same ideas to define parametrized classes and interfaces.
We can easily define a generic interface for based on the function that we defined earlier.
//the type paramter is on the interface
interface IGenericInterface<T>{
(aValue:T):T;
}
let numberEcho:IGenericInterface<number> = echo;
numberEcho(5);
In this example there is one type parameter, but we can also have interfaces with many type parameters.
Here is an example of a linked list that can store values of any type. We define both an interface and a class to represent the linked list.
// A generic reursive class to represent a linked list
interface INode<T>{
nextNode:Node<T>;
value:T;
}
//class Node<T> implements INode<T>{
//you could implement the interface but not needed here.
class Node<T>{
nextNode:Node<T>;
constructor(public value:T){};
}
Since we know how to write generic functions we can write a generic function to traverse all the nodes and display them:
// a recursive generic funciton to display all the nodes
function displayNodes<T>(n:INode<T>){
display(`The value in the first node is ${n.value}`);
if (n.nextNode !=undefined){
displayNodes(n.nextNode)
}
}
Finally, here is how we use these classes and functions.
// a linked list of integers
let n1 = new Node(0);
n1.nextNode=new Node(1);
n1.nextNode.nextNode=new Node(2);
displayNodes(n1);
// a linked list of strings
let n2 = new Node("zero");
n2.nextNode=new Node("one");
n2.nextNode.nextNode=new Node("two");
displayNodes(n2);
Type Constraints on parametrized types
Parametrized collections
If we consider the classes we have discussed so far they all use the basic types or user defined types in their construction. But what do we do when we have two classes that do essentially the same operations but only differ in the types that they operate on?