Thursday, September 17, 2015

Typescript Function Overloading

Function overloading in TypeScript is a little enigmatic and complicated to learn at first. It doesn't work like traditional overloading in other languages like C# or Java where you just create a new function with the same name but different parameters. Function overloading in TypeScript is more like defining facades for a function.

The reason TypeScript doesn't have traditional function overloading is because JavaScript doesn't support overloading. The reason JS doesn't support overloading is because JS doesn't care about function signatures. You can pass any parameters you want to a function in JS. Therefore you can't define two functions with the same name because it has no way to tell them apart.

As I said, function overloading in TypeScript is more like defining facades or interfaces to a function than overloading. The way it works is you define a function that can handle all of the different parameters that you want to throw at it, just as you would in JavaScript, then define your facades to make it easier to call that function.

An example would make it easier to see how this works. Say we want to have a getter and setter methods with the same name. First let's just write it as one function without any overloads.

class Foo{
    private _bar: string;
    public bar(val?: string): any {
        if (val || val === ""){
            this._bar = val;
        }
        else return this._bar;
    }
}

If a string parameter is passed in it will set the backing variable and return nothing. If no parameter is passed it will return the value of the backing variable. This will work, but the function signature isn't very intuitive or user friendly. So let's add some overloads, or facades, that will make it more apparent how to use it.

class Foo{
    private _bar: string;
    public bar(): string;
    public bar(val: string): void;
    public bar(val?: string): any {
        if (val || val === ""){
            this._bar = val;
        }
        else return this._bar;
    }
}

We've added two signature overloads. Notice that the overload signatures must come before the actual function body. The first is our getter which takes no parameters and returns a string. The second is our setter which takes a string parameter and has a return type of void. Both of these signatures must be compatible with the real function definition's signature.

Let's look at another example. This time we'll make a class called Color and overload the constructor so that you can either pass in a string color identifier or red, green and blue components.

class Color{
    public stringValue: string;
    constructor(def: string);
    constructor(r: number, g: number, b: number);
    constructor(rOrDef: any, g?: number, b?: number){
        if (typeof(rOrDef) === "string"){
            this.stringValue = rOrDef;
        }
        else{
            this.stringValue = "rgb("+rOrDef+","+g+","+b+")";
        }
    }
}

Notice that the first parameter rOrDef is defined as "any" so that it can take a string or number. The g and b parameters are marked as optional. Then we can simply create our two signature facades, one that takes one string parameter, and another that takes three number parameters.

Once you realize that there is no such thing as function overloading in TypeScript that's the key to understanding it. You're actually creating facades that make it easier for someone to call a function that will accept many different types of parameters.

No comments:

Post a Comment