Thursday, February 28, 2008

JavaScript Interfaces and Duck Typing

A C++ virtual class or a Java interface provides a mechanism to define a contract concept in code. We always speak of the interface as a contract between different components of software; this can provide good separation of concerns. JavaScript has no formal concept of an interface, so how can we do it?

The simplest approach is to define the contract informally and simply rely on the developers at each side of the interface to know what they are doing. Dave Thomas has given this approach the name of "duck typing" —if it walks like a duck and it quacks like a duck, then it is a duck. Similarly with our Shape interface, if it can compute an area and a perimeter, then it is a Shape.

Let's suppose we need to add areas of two shapes, in Java this could be look like:

public double addAreas(Shape s1, Shape s2){
return s1.getArea() + s2.getArea();
}

Java is type strict so the method signature will forbid passing any other objects rather than a Shape, but in JavaScript there is no guarantee :

function addAreas(s1,s2){
return s1.getArea() + s2.getArea();
}

Another way of thinking: JavaScript is highly customizable so lets plugin our interface mechanism in the Object prototype:

Object.prototype.implements=function(funcName){
return this && this[funcName] && this[funcName] instanceof Function;
}

Here we have three check points:
1. there is an Object
2. that has the specified Name ( each JS object is considered as an associative array )
3. and at last it is a function

This allows us to have our interface definition:

function isShape(obj){
return obj.implements("getArea") && bj.implements("getPerimeter");
}

If you notice the java method above there is a single check missing to complete our interface : the return value. With the current interface you can not guarantee that these two methods are returning doubles or double string representations, we can never know this in JS until we invoke the functions.

So we need another check point for the type:

function isNum(arg){
return parseFloat(arg)!=NaN;
}

At this point we have completed our Shape interface, we can rewrite our addAreas() function like this :

function addAreas(s1,s2){
var total=null;
if (isShape(s1) && isShape(s2)){
var a1=s1.getArea();
var a2=s2.getArea();
if (isNum(a1) && isNum(a2)){
total=parseFloat(a1)+parseFloat(a2);
}
}
return total;
}

we have used parseFloat here, so if a1="12" and a2="34" we don't get "1234"

In summary, duck typing keeps things simple but requires you to trust your development team to keep track of all the details. Duck typing is popular among the Ruby community. As one moves from a small team to larger projects involving separate subgroups, you can not rely on this trust, and you may want to add a few checks and balances to your code on top of duck typing.

0 comments: