Handling the unexpected - Type safe functions in Javascript

Matthias Reuter's picture

This is heavy stuff. I do not expect you to understand it, if you are a beginner in Javascript. I try to explain everything in detail, so maybe you should give it a try. I've written a jQuery plugin that makes type safe functions, so if you don't understand how it's done, you can use it nonetheless...

So what do we want? We want some way to automatedly check the given arguments by just telling which types we expect. We want to reject all other values. We want to reject the incorrect number of arguments. Let's start step by step.

var makeTypeSafe = function (f, parameterList) {
    return f;
};

We define a makeTypeSafe-function that accepts two parameters: f, the function to make type safe, and parameterList, the list of expected types of arguments of f. This function does nothing so far except returning the original function.

Now reject the wrong number of arguments:

var makeTypeSafe = function (f, parameterList) {
    // return a function that first checks the arguments before calling the
    // original function
    return function () {
        // check number of arguments
        if (arguments.length !== parameterList.length) {
            throw "Unexpected number of arguments. Expected " + p + ", got " + arguments.length;
        }
       
        // call f, passing the arguments, preserving the context
        return f.apply(this, arguments);
    };
};

Here we do no longer return the original function but a new function. This new function checks if the number of arguments (arguments.length) is different from the expected number of arguments (parameterList.length). If so, it throws an error. If not, f is called (and its return value returned).

Need an example of how to use it? Here you are.

var add = function (a, b) {
    return a + b;
};
var addIntegers = makeTypeSafe(add, ["int", "int"]);
addIntegers(21);        // will throw an error
addIntegers(21,15);     // will return 36
addIntegers("abc", 42); // will not throw an error

Why doesn't the last call throw an error? Because until now we only check the number of arguments, not the type. Let's change that.

var types = {
    "int" : function (n) {
        // by comparing n to its floor value we see if it's an integer
        return n === Math.floor(n);
    }
};

Here we define an object with one property, a function called int, which checks if a given argument is an integer. Since there is no integer type in Javascript, we do this by comparing n to its floor value (which is the same for integers).

Extending the makeTypeSafe function to check the types of arguments leads to the following code:

var types = {
    "int" : function (n) {
        return n === Math.floor(n);
    }
};

var makeTypeSafe = function (f, parameterList) {
    return function () {
        // check number of arguments
        if (arguments.length !== parameterList.length) {
            throw "Unexpected number of arguments. Expected " + p + ", got " + arguments.length + ".";
        }
       
        // check every argument using the types functions defined above
        for (var i = 0, l = arguments.length; i < l; i++) {
            if (!types[parameterList[i]](arguments[i])) {
                throw "Invalid argument at " + i + ". Argument must be of type " + parameterList[i] + ".";
            }
        }
       
        // call original function
        return f();
    };
};

That's basically the way to automatedly check parameters before executing the original function. Now we can extend the types object to accept more types:

var types = {
    "int" : function (n) {
        // by comparing n to its floor value we see if it's an integer
        return n === Math.floor(n);
    },
   
    "double" : function (n) {
        // NaN is a number as well, so check that n is not NaN
        return typeof n === "number" && !isNaN(n);
    },
   
    "string" : function (n) {
        return typeof n === "string";
    }
};

We could even add more sophisticated types like natural numbers or arrays of integers.

types["natural"] : function (n) {
    // replace > by >= if 0 is natural to you
    return types["int"](n) && n > 0;
};

types["int[]"] : function (n) {
    // accept only arrays
    if (!(n instanceof Array)) {
        return false;
    }
    // check if every element is an integer
    for (var i = 0, l = n.length; i < l; i++) {
        if (!types["int"](n[i])) {
            return false;
        }
    }
    return true;
};

"Make a jQuery plugin!" my fellow co-author Christian cried. Well, personally I don't fancy jQuery, but if you must make a function type safe, you might be using jQuery anyway, so why not? I extended the list of build-in types to support int, double (alias float), string, boolean, char, object and arrays of those types as well (used via int[], double[] etc). Furthermore, the list of of expected parameters is checked for unknown types.

I solved the original function to calculate the greatest common divisor

// Define internal gcd function
var gcd = function (a, b) {
    if (a === b) {
        return a;
    }
    if (a === 1 || b === 1) {
        return 1;
    }
    if (a < b) {
        return gcd(a, b - a);
    }
    return gcd(a - b, b);
};

// extend the known types to support natural numbers
jQuery.makeTypeSafe.types["natural"] = function (n) {
    return jQuery.makeTypeSafe.types["int"](n) && n > 0;
};

// make gcd type safe
var greatestCommonDivisor = jQuery.makeTypeSafe(gcd, ["natural", "natural"]);

// call type safe gcd function
greatestCommonDivisor(21, 15);     // returns 3
greatestCommonDivisor("21", "15"); // throws an error

Any questions, any remarks? Feel free to comment!

Trackback URL for this post:

http://united-coders.com/trackback/34
AttachmentSize
jquery.makeTypeSafe.js.txt4.55 KB

Comments

Anonymous's picture

On a sidenote and you do not want to use the typesafe function wrapper, instead of doing this:

  1. if (n === 0) {
  2.     return m;
  3.   }
  4.   if (m === 0) {
  5.     return n;
  6.   }

You can do like this:

  1. if (n === 0 || m === 0) {
  2.     return  Math.max(n,m)
  3.   }

A little cleaner IMHO.

Matthias Reuter's picture

I wanted to keep my code easy to understand, so I explicitely split this in two parts.

In a draft version I had this:

if (n * m === 0) {
  return n + m;
}

which is correct but somewhat obscure.

Anonymous's picture

That is sweet. I'm a simple douche but would have preferred that you had this code in your demo,

Anonymous's picture

You have a typo in your makeTypeSafe function: this line

  1. throw "Unexpected number of arguments. Expected " + p + ", got " + arguments.length;

contains 'p', which should be parameterList.length.

Btw, I have done similar stuff, yet not completely generic so far. However, I preferred using object references instead of strings for types, like:

  1. function ObjectA(...) { ... }
  2.  
  3. makeTypeSafe(f, [ObjectA]);

This makes the type checking functions superfluous (note, however, that basic types like int are somewhat sloppy in javascript, so you will require explicit casting to an object variant for these).