Saturday, 10 June 2017

Generics in TypeScript


The ability to build reusable components in a programming language enables developers to be DRY while coding. This also helps reduce the software complexity and is much easier to maintain. Each reusable component can also be tested for edge cases which makes the code robust.


Generic Functions:

In TypeScript generic functions can be useful for implementing factories. The following example shows how generic functions can be implemented:
function getType<T>(input: T): string{
   return typeof input;
}


getType("hello"); //string
getType(100); //number
getType({'city': 'Sanfrancisco', 'id': 1}); //object


In this example the function 'getType' can take any type as an argument. So the argument "hello" will return a 'string' type. Similarly the arguments 100 and {'city': 'Sanfrancisco', 'id': 1} will return 'number' and 'object' types respectively. 

Generic Classes:

Using generic classes developers can constrain the type used by a class. Also it enables developers to use a single class for various purposes by changing the type and method definition.


class BasicCalculator<T>{
    add: (value1:T, value2:T) => T;
    multiply: (value1:T, value2:T) => T;
    subtract: (value1:T, value2:T) => T;
    divide: (value1:T, value2:T) => T;
}



///Using number type.

let calc = new BasicCalculator<number>();

calc.add = function(a, b){ return a+b;}
calc.multiply = function(a, b){ return a*b;}
calc.subtract = function(a, b){ return Math.abs(a-b);}
calc.divide = function(a, b){ if(a==0){ return 0; } if(b!==0){ return a/b; } return -1;}

console.log(calc.add(1, 3)); //4
console.log(calc.multiply(1, 3)); //3
console.log(calc.subtract(1, 3)); //2
console.log(calc.divide(1, 3)); //0.33333...


///Using string type.

let calcString = new BasicCalculator<string>();

calcString.add = function(a, b){ return a.length+b.length+"";}
calcString.multiply = function(a, b){ return a.length*b.length+"";}
calcString.subtract = function(a, b){ return Math.abs(a.length-b.length)+"";}
calcString.divide = function(a, b){ if(a.length==0){ return 0+""; } if(b.length!==0){ return (a.length/b.length)+""; } return -1+"";}

console.log(calcString.add("Apple", "Banana")); //11
console.log(calcString.multiply("Apple", "Banana")); //30
console.log(calcString.subtract("Apple", "Banana")); //1
console.log(calcString.divide("Apple", "Banana")); //0.833333...

The examples shows how 'BasicCalculator' can be instantiated with multiple types and when instantiated with a particular type it is constrained only that one. 

Generic Interfaces:

Similar to generic classes the generic interfaces can be used to implement methods that process object of only a certain type. Generic interfaces can more concise than generic classes by being able to provide a general signature for several implementations. 
interface identityInterface<T> {
    (arg: T): T;
}

function customIdentityFunction<T>(arg: T): T {
    return arg;
}

let newIdentityNum: identityInterface<number> = customIdentityFunction;

console.log(newIdentityNum(45));//45

let newIdentityObj: identityInterface<object> = customIdentityFunction;

console.log(newIdentityObj({'name': 'Phil', 'id': 76}));//{'name': 'Phil', 'id': 76}

let newIdentityStr: identityInterface<string> = customIdentityFunction;

console.log(newIdentityStr("test"));//"test"


The 'identityInterface' provides a general signature for multiple implementations. For instance setting type to 'number' will constraint 'customIdentityFunction' to only return a number.Similarly the types object and string will constraint the function to the respective type.

No comments:

Post a Comment