BAM Weblog

JavaScript ad hoc single dispatch library

Brian McKenna2012-07-15

The idea in ad hoc polymorphism is to separate functions from data, in contrast to mutating prototypes or the data itself.

We can of course just define a function that uses duck typing but that's only if the data quacks the same. It also doesn't allow open extension of the function implementation.

Ad hoc polymorphism is very common in functional languages like Lisp and Haskell.

I wrote a little library for ad hoc polymorphism in JavaScript. It's single dispatch, meaning it will only choose which function to execute based off of the first argument. It's trivial to extend the code below to use mutliple dispatch.

Here's what using the library looks like:

var x = emptyEnvironment
    // Empty
    .addMethod('empty')
    .extend('empty', isArray, function() {
        return [];
    })
    .extend('empty', isString, function() {
        return '';
    })
    // Append
    .addMethod('append')
    .extend('append', isArray, function(a) {
        return function(b) {
            return a.concat(b);
        };
    })
    .extend('append', isString, function(a) {
        return function(b) {
            return a + b;
        };
    });

console.log(x.append([1, 2, 3])([2, 3, 4])); // [1, 2, 3, 2, 3, 4]
console.log(x.append("TEST")("HELLO")); // TESTHELLO
console.log(x.empty([1, 2, 3])); // []

Here's the complete implementation:

// ## Mutable functions
// Functions that use mutable state *only* belong here. They must
// provide an immutable API.
function extend(o, e) {
    var n = {}, i;
    for(i in o) {
        n[i] = o[i];
    }
    for(i in e) {
        n[i] = e[i];
    }
    return n;
}

function singletonObject(k, v) {
    var o = {};
    o[k] = v;
    return o;
}

function foldl(a, f, z) {
    var i;
    for(i = 0; i < a.length; i++) {
        z = f(z, a[i]);
    }
    return z;
}

// ## Helper predicates
function isArray(a) {
    if(Array.isArray) return Array.isArray(a);
    return Object.prototype.toString.call(a) === "[object Array]";
}

function isTypeOf(s) {
    return function(o) {
        return typeof o == s;
    };
}

var isString = isTypeOf('string');
var isNumber = isTypeOf('number');

// ## An environment holds all modules and methods.
var emptyEnvironment = {
    methods: {},
    addMethod: function(methodName) {
        return extend(
            extend(
                this,
                singletonObject(methodName, function(o) {
                    return foldl(this.methods[methodName], function(accum, method) {
                        if(method.predicate(o)) {
                            return method.implementation;
                        }
                        return accum;
                    }, function() {
                        throw new Error("Method not implemented for this input");
                    })(o);
                })
            ), {
                methods: extend(
                    this.methods,
                    singletonObject(methodName, [])
                )
            }
        );
    },
    extend: function(methodName, predicate, implementation) {
        return extend(this, {
            methods: extend(
                this.methods,
                singletonObject(
                    methodName,
                    this.methods[methodName].concat({
                        predicate: predicate,
                        implementation: implementation
                    })
                )
            )
        });
    }
};
Please enable JavaScript to view the comments powered by Disqus.