Ad-hoc method extension

JavaScript performance comparison

Revision 2 of this test case created

Info

CEnvironment allows extension by overloading on the input constructor:

var env1 = adhoc.CEnvironment()
    .method('copoint', adhoc.Id, function(x) { return x.value; })
    .method('copoint', adhoc.NonEmptyArray, function(x) { return x.values[0]; });

MEnvironment allows extension by overloading with a predicate:

var env2 = adhoc.MEnvironment()
    .method('copoint', adhoc.isInstanceOf(adhoc.Id), function(x) { return x.value; })
    .method('copoint', adhoc.isInstanceOf(adhoc.NonEmptyArray), function(x) { return x.values[0]; });

The predicate is obviously more flexible but is likely to be extremely slow. The constructor test is possibly O(1) if the environment has Harmony Map support.

Preparation code

<script>
var adhoc = (function() {
    "use strict";

    var Map = Map || polyfillMap();

    function polyfillMap() {
        var Map = function Map() {
            var self = getInstance(this, Map);
            self.keys = [];
            self.values = [];
            return self;
        };

        // O(n)
        // Would be O(1) when native (i.e. not a polyfill)
        Map.prototype.get = function(k) {
            return this.values[this.keys.indexOf(k)];
        };

        Map.prototype.set = function(k, v) {
            this.keys.push(k);
            this.values.push(v);
        };

        return Map;
    }

    function create(proto) {
        function Ctor() {}
        Ctor.prototype = proto;
        return new Ctor();
    }
   
    function getInstance(self, constructor) {
        return self instanceof constructor ? self : create(constructor.prototype);
    }

    function isInstanceOf(c) {
        return function(o) {
            return o instanceof c;
        };
    }

    function Id(value) {
        var self = getInstance(this, Id);
        self.value = value;
        return self;
    }

    function NonEmptyArray(values) {
        var self = getInstance(this, NonEmptyArray);
        if(!values.length) throw new TypeError("NonEmptyArray got empty array");
        self.values = values;
        return self;
    }

    function CEnvironment(methods) {
        var self = getInstance(this, CEnvironment),
            key;

        function makeMethod(key) {
            return function(x) {
                return (x.constructor.name ?
                        methods[key].obj[x.constructor.name] :
                        methods[key].get(x.constructor)).apply(this, arguments);
            };
        }

        methods = methods || {};
        self.methods = methods;
        for(key in methods) {
            if(self[key]) throw new TypeError(key + ' already exists!');
            self[key] = makeMethod(key);
        }

        return self;
    }
    CEnvironment.prototype.method = function(name, constructor, implementation) {
        var methods = {},
            key;

        for(key in this.methods) {
            methods[key] = this.methods[key];
        }

        if(!methods[name]) methods[name] = {
            map: new Map(),
            obj: {}
        };

        if(constructor.name) {
            methods[name].obj[constructor.name] = implementation;
        } else {
            methods[name].set(constructor, implementation);
        }

        return CEnvironment(methods);
    };

    function MEnvironment(methods) {
        var self = getInstance(this, MEnvironment),
            key;

        function makeMethod(key) {
            return function() {
                var i;
                for(i = 0; i < methods[key].length; i++) {
                    if(!methods[key][i].predicate.apply(this, arguments)) continue;
                    return methods[key][i].implementation.apply(this, arguments);
                }
                throw new TypeError(key + " isn't implemented for this input");
            };
        }

        methods = methods || {};
        self.methods = methods;
        for(key in methods) {
            if(self[key]) throw new TypeError(key + ' already exists!');
            self[key] = makeMethod(key);
        }

        return self;
    }
    MEnvironment.prototype.method = function(name, predicate, implementation) {
        var methods = {},
            key;

        for(key in this.methods) {
            methods[key] = this.methods[key];
        }

        if(!methods[name]) methods[name] = [];

        methods[name].unshift({
            predicate: predicate,
            implementation: implementation
        });

        return MEnvironment(methods);
    };

    return {
        isInstanceOf: isInstanceOf,
        Id: Id,
        NonEmptyArray: NonEmptyArray,
        CEnvironment: CEnvironment,
        MEnvironment: MEnvironment
    };
})();

var env1 = adhoc.CEnvironment()
    .method('copoint', adhoc.Id, function(x) { return x.value; })
    .method('copoint', adhoc.NonEmptyArray, function(x) { return x.values[0]; });

var env2 = adhoc.MEnvironment()
    .method('copoint', adhoc.isInstanceOf(adhoc.Id), function(x) { return x.value; })
    .method('copoint', adhoc.isInstanceOf(adhoc.NonEmptyArray), function(x) { return x.values[0]; });
</script>

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
Constructor check
env1.copoint(adhoc.Id(42));
env1.copoint(adhoc.NonEmptyArray([142, 152, 162]));
pending…
Multimethods
env2.copoint(adhoc.Id(42));
env2.copoint(adhoc.NonEmptyArray([142, 152, 162]));
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

0 comments

Add a comment