Bringing “static” type-checking to JavaScript

It looks like my language to learn for this year is Java. Last year (well, tail end of 2006) is was Ruby and Rails, and I’m very much still learning as far as those are concerned. I also got heavily into JavaScript; I’d done a little jQuery before but 2007 was the year I really got to grips with the core JavaScript language as well as other libraries and APIs. I started as a JavaScript developer at a Java web shop late last year, and we’re all doing SCJP together at the moment. Whatever you think of certification, it never hurts to learn a few new tricks and see how different people think.

(Also, before I start in: in general, for the work I do, you can pry dynamic typing from my cold, dead hands. I can totally see an argument for static typing in large programs, and this is an exercise in bringing ideas from one language to another. Whether this is of any use to anybody is of secondary importance.)

So, at the risk of over-indulging my inner programmer’s shiny-new-toy syndrome, I was wondering how you might go about automating type-checking routines in JavaScript code. This is something you need to routinely do anyway, whether it’s checking for an explicit type or duck-type property/method checking. Java lets you specify the types/interfaces of method arguments and returns values, and will produce compile-time errors if you write code that flouts the type requirements you’ve put in place. JavaScript has no such thing as compile-time (in the sense that you can know whether your program will work before running it), but we can come up with something that clearly documents your code and produces runtime errors if it misbehaves. I’ll lead with the code and explain afterwards:

(function() {
  var interfaces = !!(typeof JS != 'undefined' && JS.Interface);
  
  var signaturesMatch = function(expected, actual) {
    var n = expected.length, valid, a, b;
    for (var i = 0; i < n; i++) {
      a = actual[i]; b = expected[i]; valid = true;
      switch (true) {
        case b instanceof Function :
          valid = a ? (a.isA ? a.isA(b) : (a instanceof b)) : false;
          break;
        case interfaces && b instanceof JS.Interface :
          valid = b.test(a);
          break;
        case typeof b == 'string' || b instanceof String :
          valid = (typeof a == b);
          break;
      }
      if (!valid) return false;
    }
    return true;
  };
  
  Function.prototype.expects = function() {
    var method = this, args = arguments;
    return function() {
      if (!signaturesMatch(args, arguments))
        throw new Error('Invalid argument types');
      return method.apply(this, arguments);
    };
  };
  
  Function.prototype.returns = function(type) {
    var method = this;
    return function() {
      var result = method.apply(this, arguments);
      if (!signaturesMatch([type], [result]))
        throw new Error('Invalid return type');
      return result;
    };
  };
})();

First, some stuff that might look totally foreign: that a.isA(b) business and all the stuff about interfaces is to provide support for type-checking features of JS.Class, my JavaScript OO framework. Hop over to its site if you need some explanation.

The signaturesMatch() function (contained inside a closure so it’s not global) compares a list of types (and interfaces) with a list of values, and returns true only if they match. It smooths over the details of whether to use typeof or instanceof, does interface checking if it’s available, and other niceties. So:

signaturesMatch(
  ['string', Array, 'boolean'],
  ['foo', [0,1,2], false]
)

// -> true

signaturesMatch(
  ['number'],
  [{someObject: 'value'}]
)

// -> false

Finally, we get two new methods added to Function: expects() and returns(). You use these to specify types of arguments and return values. If a function is called with the wrong types of arguments or returns something odd you’ll get a runtime error. For example:

var add = function(a, b) {
  return a + b;
}
.expects('number', 'number')
.returns('number');

An incredibly trivial example but it illustrates the point of all this: anyone reading your function knows exactly how its interface works, and knows that if it misbehaves the program will complain loudly.

There are some problems, in that it creates a few extra method calls and it’s a step short of what I really wanted to do, which was to implement an elegant system for method overloading. Still, it’s a step in the right direction…

Update: I originally said this wouldn’t work in class definitions, as methods wouldn’t know what this should refer to. This has now been fixed.

If you've enjoyed this article, you might enjoy my recently published book JavaScript Testing Recipes. It's full of simple techniques for writing modular, maintainable JavaScript apps in the browser and on the server.