Generic programming with TypeScript

Programmers coming from C++ identifies a generic function as a template. A generic function can process any type of data that is passed. Truly speaking generic functions can multiplex the code execution based on type of data supplied. In TypeScript, we can have a good variations of how generics can be implemented. If some one asks us to develop a generic function, using knowledge we gained till now this might be a solution.

function genericFunction(arg:any) : any{
    return arg;
}

console.log(genericFunction(4));

Using any keyword we can do the universal processing of any type of element. But this implementation has a flaw. This is actually what a normal JavaScript function can do. By default all JavaScript functions can take any type of arguments and can return any type. A true TypeScript generics usage lies in it's conditioning of arguments and their properties. The above example can be written in TypeScript as

function genericFunction<T>(arg:T): string {
        return arg.toString();
}

console.log(genericFunction(24));
console.log(genericFunction(24.666));
console.log(genericFunction("Naren"));

Syntax is similar to C++ template. Here T is the generic type that is passed to the function.

// reverse_generic_array.ts
function reverseArray<T>(arg1:T[]): T[]{
        return arg1.reverse();
}

console.log(reverseArray([1,2,3]));
console.log(reverseArray([1.6,2.2,3.8]));
console.log(reverseArray(["Saikiran","Naren","Manoj"]));
$ tsc reverse_generic_array.ts
$ node reverse_generic_array.js
[3,2,1]
[3.8,2.2,1.6]
["Manoj", "Naren", "Saikiran"]

Here we are creating a generic function that reverses an array passed to it. Operation is a simple one but if we observe, the generic function is also having a return type. Here return type is an array.

In scripting languages data processed by function can be altered. So we need a filter to stop returning non-uniform data. TypeScript provides us the filter.

Multiple generic parameters

We can have any number of generic arguments for a function. For example consider the below function.

function printNumberAlphabets<T1,T2>(arg1:T1[], arg2:T2[]): void{
arg1.forEach((element, index) => {
    console.log(element, "is", arg2[index]);
    });
}

printNumberAlphabets([1,2,3], ["one","two","three"]);

Here we are passing arrays of integer and strings to the function printNumberAlphabets.Since we are printing and not returning anything back, void is the correct type of return. From JavaScript ES5 forEach takes a function with element and index as parameters and iterates over it.

Assuming properties on generic functions

JavaScript is the language where we can play with functions equally as variables. It is quite obvious of using a property of the type in function. Like length property of an Array. Consider a situation where we need to access a property on generic variable.

var somePoint = {x:10, y:30};
function xValue<T>(arg: T): number {
    return arg.x;
}

console.log(xValue(somePoint));

From your guess this will work fine and gives 10 as the x-coordinate. But TypeScript throws error complaining Error: T doesn't have property x. .

So in order to notify generic function about the property TypeScript takes help of interfaces. We can define a simple interface here and will see detail discussion of them in coming chapters.

// generic_type_with_property.ts
interface Point {
x: number;
y: number;
}

var someIntegerPoint = {x:10, y:30};
var someFloatPoint = {x:10.6, y:30.3};

function xValue<T extends Point>(arg: T): number {
    return arg.x;
}

console.log(xValue(someIntegerPoint));
console.log(xValue(someFloatPoint));

Now TypeScript transpiler (compiler + translator) won't throw an error because we explicitly extended interface called Point. We all know that a class extending an interface should have all properties (variables, functions) in them. So here generic type T extending the Point which has x,y properties. Using this technique , we can design our logic in the generic function.