Mastering the Arcane Art of Javascript-mancy for C# Developers - Chapter 5: Even More Useful Function Patterns - Function Overloading

| Comments

The Mastering the Arcane Art of JavaScript-mancy series are my humble attempt at bringing my love for JavaScript to all other C# developers that haven’t yet discovered how awesome this language and its whole ecosystem are. These articles are excerpts of the super duper awesome JavaScript-Mancy book a compendium of all things JavaScript for C# developers.

In the last couple of articles we learned some useful patterns with functions in JavaScript that helped us achieve default arguments and multiple arguments and we saw how ECMAScript 2015 (ES6) can simplify the way we can incorporate either of these in our code.

Today I will close this section – useful function patterns – with some tips on how you can achieve function overloading in JavaScript. Hope you like it!

The Problem with Function Overloading in JavaScript

One does not simply overload functions in JavaScript

There’s a slight problem when you attempt to do function overloading in JavaScript like you would in C#… you can’t do it:

1
2
3
4
5
6
7
8
9
10
11
12
13
// One does not simply overload functions in JavaScript willy nilly
console.log("one does not simply overload functions in JavaScript")
function raiseSkeleton(){
    console.log('raise skeleton!!!');
}

function raiseSkeleton(mana){
    console.log('raise skeleton has been overwritten!!!');
}

raiseSkeleton();
// => raise skeleton has been overwritten!!!
console.log("functions get overwritten if you declare them multiple times, even if they have different signatures");

In JavaScript, you cannot override a function by defining a new function with the same name and a different signature, if you try to do so, you’ll just succeed in completely overwriting your original function with a new implementation.

How Do We Achieve Function Overloading In JavaScript Then?

Well, as with many things in JavaScript, you’ll need to take advantage of the flexibility and freedom the language gives you to emulate function overloading yourself. One common pattern for achieving function overloading is to use the arguments object and inspect the arguments that are passed into a function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Instead
// #1 you can do argument inspection, which sucks
// - less readable and maintainable
function raiseSkeletonWithArgumentInspecting(){
    if (typeof arguments[0] === "number"){
        raiseSkeletonsInNumber(arguments[0]);
    } else if (typeof arguments[0] === "string") {
        raiseSkeletonCreature(arguments[0]);
    } else {
        console.log('raise a skeleton');
    }

    function raiseSkeletonsInNumber(n){
        console.log('raise ' + n + ' skeletons');
    }
    function raiseSkeletonCreature(creature){
        console.log('raise a skeleton ' + creature);
    };
}

Following this pattern you inspect each argument being passed to the overloaded function (or even the number of arguments) and determine which internal implementation to execute:

1
2
3
4
5
6
raiseSkeletonWithArgumentInspecting();
// => raise a skeleton
raiseSkeletonWithArgumentInspecting(4);
// => raise 4 skeletons
raiseSkeletonWithArgumentInspecting('king');
// => raise skeleton king

This approach can get very unwieldy, very fast. As the overloaded functions and their parameter increase in number, the function becomes harder and harder to read, maintain and extend.

At this point you may be thinking: “…checking the type of the arguments being passed?? seriously?!?” and I concur with you, that’s why I like to use this next approach instead.

A better way to achieve function overloading is to use an options object that acts as a container for the different parameters a function can consume:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// #2 or use an object to wrap parameters with rules
// - it gives you named arguments
// - and painless extension if more arguments are required
function raiseSkeletonWithOptions(spellOptions){
    spellOptions = spellOptions || {};
    var armySize = spellOptions.armySize || 1,
        creatureType = spellOptions.creatureType || '';

    if (creatureType){
        console.log('raise a skeleton ' + creatureType);
    } else {
        console.log('raise ' + armySize + ' skeletons ' + creatureType);
    }

}

raiseSkeletonWithOptions();
// => raise a skeleton
raiseSkeletonWithOptions({armySize: 4});
// => raise 4 skeletons
raiseSkeletonWithOptions({creatureType:'king'});
// => raise skeleton king

This is not strictly function overloading but it provides the same benefits, it gives you different APIs for a given function, and additionally, named arguments and easy extensibility (as you can add new options without breaking any clients).

Here is an example of the options object pattern in the wild, the jQuery ajax function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Main method
    ajax: function( url, options ) {

        // If url is an object, simulate pre-1.5 signature
        if ( typeof url === "object" ) {
            options = url;
            url = undefined;
        }

        // Force options to be an object
        options = options || {};

        var transport,
            // URL without anti-cache param
            cacheURL,
            // Response headers
            responseHeadersString,
            responseHeaders,
            // timeout handle
            timeoutTimer,
        // etc lots of codes
    }

ECMAScript 2015 Helps You Out with Function Overloading

Although ES2015 doesn’t come with classic function overloading it comes with default arguments which give you better support for function overloading than what we’ve had so far:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function castIceCone(mana=5, {direction='in front of you'}={}){
    var caster = this || 'God almighty';

    // new template strings
    console.log(`${caster} spends ${mana} mana and casts a terrible ice cone ${direction}`);
}

castIceCone();
// => God almighty spends 5 mana and casts a terrible ice cone in front of you

var jaime = {
    toString: function(){return 'Jaime the Mighty';},
    castIceConePro: castIceConePro
};
jaime.castIceConePro(10, { direction: 'towards Mordor'})
// => Jaime the Mighty spends 10 mana and casts a terrible ice cone towards Mordor

A Glimpse Into Functional Programming and Polymorphic Functions

Yet another interesting pattern for achieving function overloading in JavaScript is to rely on JavaScript great support for functional programming. In the world of functional programming there’s the concept of polymorphic functions, that is, functions which exhibit different behaviors based on their arguments.

Let’s start by decomposing the previous examples into smaller functions:

You can experiment with this example directly within this jsBin or downloading the source code from GitHub.

1
2
3
4
5
6
7
8
9
10
11
function raiseSkeletons(number){
    if (Number.isInteger(number)){ return `raise ${number} skeletons`;}
}

function raiseSkeletonCreature(creature){
    if (creature) {return `raise a skeleton ${creature}`;}
}

function raiseSingleSkeleton(){
    return 'raise a skeleton';
}

And now we create an abstraction (functional programming likes abstraction) for a function that executes several functions in sequence until one returns a valid result (different from undefined):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// this is a higher order function
// that returns a new function
// something like a function factory
// we could reuse it to our heart's content
function dispatch(...fns){

    return function(...args){
        for(let f of fns){
          let result = f.apply(null, args);
          if (exists(value)) {return result;}

        }
    };
}

function exists(value){
    return value !== undefined
}

Once we have defined the dispatch function, we can use it to create a raiseSkeletonFunctionally polymorphic function that behaves in different fashions based on the arguments it takes. The function will delegate to the each special raise skeleton function until a suitable result is obtained. The last raiseSingleSkeleton is a catch-all function that will return a result every time raiseSkeletonFunctionally is called.

1
2
3
4
5
6
7
8
let raiseSkeletonFunctionally = dispatch(raiseSkeletons, raiseSkeletonCreature, raiseSingleSkeleton);

console.log(raiseSkeletonFunctionally());
// => raise a skeleton
console.log(raiseSkeletonFunctionally(4));
// => raise 4 skeletons
console.log(raiseSkeletonFunctionally('king'));
// => raise a skeleton king

A super duper mega cool thing that you may or may not have noticed is the awesome degree of composability of this approach. If we want to extend this function later own, we do it without modifying the original function. Take a look at this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// But wait! There is more!
// Let's extend our raise skeleton function!
function raiseOnSteroids({number=0, type='skeleton'}={}){
  if(number){return `raise ${number} ${type}s`;}
}

let raiseAdvanced = dispatch(raiseOnSteroids, raiseSkeletonFunctionally);

console.log(raiseAdvanced());
// => raise a skeleton
console.log(raiseAdvanced(4));
// => raise 4 skeletons
console.log(raiseAdvanced('king'));
// => raise a skeleton king
console.log(raiseAdvanced({number: 10, type: 'ghoul'}))
// => raise 10 ghouls

// AWESOME

This is the OCP (Open-Closed Principle)1 in full glory. Functional programming is pretty awesome right? We will take a deeper dive into functional programmming later within the series and you’ll get the chance to experiment a lot more with higher-order functions and function composition alike. But if you can’t wait, don’t let me stop you, by all means, jump on!

Concluding

Although JavaScript doesn’t support function overloading you can achieve the same behavior by using different approaches.

You can either take advantage of the arguments object and inspect the arguments that are being passed to a function at runtime, solution that you should only use with the simplest of implementations as it becomes unwieldly and hard to maintain as parameters and overloads are added to a function.

Or you can use an options object as a wrapper for parameters, solution that is both more readable and maintanaible and which provides two additional benefits: named arguments and a lot of flexibility to extend the function with new arguments.

ES2015 brings improved support for function overloading with default arguments.

Finally, you can take advantage of functional programming,compose your functions from smaller functions and use a dispatching mechanism to select which function is called based on the arguments.

And that is that :). Hope you have enjoyed reading the article. Have a nice day! See you next time with functions in ECMAScript 2015 and JavaScript’s version of LINQ.

Interested in Learning More JavaScript? Buy the Book!

Are you a C# Developer interested in learning JavaScript? Then take a look at the JavaScript-mancy book, a complete compendium of JavaScript for C# developers.

a JavaScriptmancy sample cover

More Articles in These Series


  1. Open for extension and closed for modification. http://bit.ly/ocp-wikipedia

Comments