Functions
Now we come to the fundamental building block of most programming languages the function.
function sqr(n){
return n*n;
}
TypeScript will infer the types of the parameters of the function and the return type of the function.
In this section as in much of this document I have taken the approach that we should try and understand all the features of TypeScrip. This is especially true of the TypeScript type system.
Do we need to specify all the type information or should we let the compiler infer the types for us? I think the answer is very subjective, so you must choose for your self. I would recommend that you fully understand they way the type system works and how to do all the declarations explicitly. Then decide based on your programming style and the kind of project you are working on.
There is a style of programming with types similar to test driven development (TDD) where you invest in writing tests up front. Similarly, you invest the effort to specify the type information of your functions upfront. This forces you to think abstractly about your program in terms of types. (Thinking of the types as Lego blocks and ensuring that the types fit together is an interesting way of looking at your program.) Now, when you write the implementation the compiler will check your implementation against your type definition and verify if you have made any mistakes. Not all errors in programs are type errors and you still need to write tests, but the compiler catches a lot of problems in your code. In fact I think that a large percentage of the tests that exist are because people are working in languages that do not have a static type system to help them verify their programs.
Now, let us see how we can be explicit and specify all the type information ourselves. We first declare a function by specifying its signature explicitly and then assign it to an implementation:
//defining a variable with the function signature
var sqr: (n: number)=>number
//initalizing the previously defined variable
sqr=function (n){
return n*n;
}
( Note:The variable sqr
can be assigned any function that matches its defintion i.e, a function that takes a number and returns a number. (Of course if you do so the name sqr does not make sense any more, but that is a different issue.)
You can do this more compactly in one line as:
//defining a variable with the function signature
var sqr: (n: number)=>number = function (n:number):number{
return n*n;
}
Functions: optional parameters
Functions in TypeScript have support for default parameters. Contrast this to JavaScript where all parameters are optional and any parameter that is left out will have the value undefined
.
In TypeScript the compiler will check to make sure that the right number of parameters are specified and that they have "sensible" values. Of course this checking is useful in most cases except when we want to make some parameters optional. In TypeScript you can support optional parameters by designating some parameters as optional using the ?
annotation.
Consider the following example:
function getRange(start, end, step?) {
var range = [];
if (!step) {
step = 1;
}
for (var i = start; i < end; i = i + step) {
range.push(i);
}
return range;
}
Here are you can see the parameter step
is declared optional by adding the ?
to the parameter. This means that you can invoke the function without specifying a value for this parameter.
The optional parameters have to be specified at the end of the parameter list, after you have specified the non-optional parameters.
Within the function you may need to check if the step
parameter is provide and write the logic to handle the situation when the function is invoked without the parameter as shown in this example.
Functions: default parameters
As you saw in the previous example making a parameter optional means that you may have to write the logic to check if this parameter has been provided and then provide the default value yourself. You do this easily with default parameters.
Here is the same getRange
example using default parameters.
function getRange(start=0, end, step=1) {
var range = [];
for (var i = start; i < end; i = i + step) {
range.push(i);
}
return range;
}
//note if the optional parameter is not at the end you have
//to explicitly set it to undefined, you cannot just leave it out.
//here start is set to default value using undefined
//and step is set to default value by ommiting it
display(getRange(undefined, 5));
In this example, the parametersstart
and step
would be optional and will get the default value of 0 and 1 respectively. Note The default initialized parameters need to be at the end of the parameter list if you want it to be optional. This becomes clear when you look at the inferred function signature:
function myRange(start:number, end:any, step?:number):any[]
Notice how the compiler infers that start
is not an optional parameter due to its location while step
is an optional parameter.
Functions: rest parameters
TypeScript functions are variadic, they can have a variable number of parameters. TypeScript has support for a rest
parameter (like *args
in Python).
Here is an example:
function makeGreeting1(firstName, lastName, ...otherNames) {
var greeting = "Hello " + firstName + " " + lastName;
for (var name of otherNames) {
greeting += " " + name;
}
return greeting;
}
display(makeGreeting("Jim", "Brown", "of", "Norwalk"));
In this example the last parameter is a rest parameter and will have the type any[]
. You can specify this array as an array with the required type of your choice as follows:
function makeGreeting(firstName:string, lastName:string, ...otherNames:string[]){
...
...
}
The function declaration looks like this:
let makeGreeting:(fName:string, lName:string, ...rest:string[])=>string;
Functions: overloads
Now, we come to the idea of function overloads. TypeScript does not have strong support for function overloads, where you can have multiple implementations with the same function name but different parameters. (Some of this lack of support is due to the constraint that the JavaScript does not support this. And some of this is a design choice. Since you can do a lot more if you really wanted to implement a function/method overloading scheme even within javaScript (see thislibrary by John Resig)
Here is an example of function overloading:
function makeName(id: number);
function makeName(name: string);
function makeName(x: number | string): string {
if (typeof x == 'number') {
return "return name for id from database";
}
else if (typeof x == 'string') {
return "making name for " + x;
}
}
Here the function makeName
has two overloads and one common implementation, that handles all the cases. Additionally the compiler searches for overloads in the order in which they are defined so that does make an impact on which function is selected.
I do not find using function overloads and writing all the type checking required to support this easy to do, and so I do not recommend using this too much. You can handle many of the cases that seem to need overloads using default parameters or optional parameters. In general I would recommend just making new functions with different names.
Lambda functions and closures
TypeScript has support for Lambda functions or anonymous functions. Here is an example where we have assigned the Lambda function to a variable.
let sqr = (n:number)=> {return n*n;}
But this form has special significance in terms of what the this
variable refers to. We will get to this in the next section.
Now here is a simple example of a closure:
function makeStepShifter(step){
return (x)=>{return x+step;}
}
let shiftByOne = makeStepShifter(1);
display(`2 shiftByOne is : ${shiftByOne(2)}`)
let shiftByTwo = makeStepShifter(2);
display(`2 shiftByTwo is : ${shiftByTwo(2)}`)
As you can see the function makeStepShifter
returns a function that has captured the local variable step
as we call this a "closure". This capture of state is a very interesting concept especially when it comes to asynchronous programming.
The this
parameter for Lambda functions
Here we are explicitly using the this variable to reference a captured local variable.
//the this variable
let makeRandomShifter = {
numbers: [1, 2, 3, 4, 5, 6],
makeShifter: function () {
return (x) => {
return x + this.numbers[Math.floor(Math.random() * 6)];
};
}
}
let shiftRandom = makeRandomShifter.makeShifter();
display(`2 with a Random shift is : ${shiftRandom(2)}`)
If we do not use the lambda function but instead we use the function form:
//the this variable
let makeRandomShifter = {
numbers:[1,2,3,4,5,6],
makeShifter:function(){
return function(x){
return x+this.numbers[Math.floor(Math.random() * 6)];
};
}
}
The program will not work because the this variable is not set when the function is captured. Instead the this
variables is determined when executed and the it is either set to Window or undefined (if you are running in the "use strict" mode.)
For the lambda function, the this
variable is set at the time of capture.