Skip to content

Instantly share code, notes, and snippets.

@roobie
Last active January 4, 2016 20:49
Show Gist options
  • Save roobie/8677191 to your computer and use it in GitHub Desktop.
Save roobie/8677191 to your computer and use it in GitHub Desktop.
Multiple dispatch
var multi = (function multimethod_closure() {
/*
Usage:
var m = multi()
.when_t(String)(function (s) {console.log('string');})
.when_t(Number)(function (s) {console.log('number');})
*/
"use strict";
var WHEN_TYPE = "type",
WHEN_VALUE = "value",
NOOP = function () { },
IDENTITY = function (a) { return a; },
MULTIDENTITY = function () { return slice(arguments); };
var slice = Function.prototype.call.bind(Array.prototype.slice);
var fluent = function fluent_decorator(fn) {
return function () {
fn.apply(this, arguments);
return this;
};
};
var get_ctor = function get_constructor(a) {
if(a === void 0 || a === null) {
return void 0;
}
return a.constructor;
};
var multi = function multi(conf) {
conf = conf || {};
var lookup = function lookup(type, args) {
// TODO: if dispatching on type, maybe build an algo that checks for most specific.
// I.e. if we have a constructor `Animal` that another constructor (e.g. `Bear`) whose
// prototype's [[__proto__]] points to, and user have dispatch on both types,
// an argument of type `Bear` should always cause the most specific fn to be invoked.
// It is possible with this impl, only the user have to define the least specific
// type to dispatch on last, that is:
// multi().ontypes(Bear)(func...).ontypes(Animal)(func...)
// otherwise the dispatch on `Animal` would catch all calls.
var fn_table = multimethod.function_table,
args_len = args.length,
match = false,
i, j;
var comparison;
if (type === WHEN_TYPE) {
comparison = function instance_of_type(instance, type) {
if (!type.prototype) { return false; }
return get_ctor(instance) === type || type.prototype.isPrototypeOf(instance);
};
} else if (type === WHEN_VALUE) {
comparison = function strict_equals(a, b) {
return a === b;
};
} else {
comparison = function false_func() { return false; };
}
for (i = 0; i < fn_table.length; i++) {
if (fn_table[i].args.length !== args_len || fn_table[i].type !== type) {
continue;
}
for (j = 0; j < args_len; j++) {
if (!comparison(args[j], fn_table[i].args[j])) {
match = false;
break;
}
match = true;
}
if (match) { return fn_table[i]; }
}
return void 0;
};
var multimethod = function multimethod() {
var args = slice(arguments, 0),
found_it;
found_it = lookup(WHEN_VALUE, args) || lookup(WHEN_TYPE, args);
if (!found_it && !multimethod._fallback) {
throw new TypeError("No match!");
}
if (found_it) {
return found_it.func.apply(this, args);
} else if (multimethod._fallback) {
return multimethod._fallback.apply(this, args);
}
throw new Error("Should not happen");
};
var get_when_func = function get_when_func(type) {
return function multi_on() {
var self = multimethod,
args = slice(arguments, 0);
return function multi_on_callback(func, conf) {
conf = conf || {};
self.function_table.push({
type: type || WHEN_TYPE,
args: args || [],
func: func || NOOP,
conf: {
before: conf.before || MULTIDENTITY,
after: conf.after || IDENTITY
}
});
return self;
};
};
};
Object.defineProperties(multimethod, {
_configuration: {
value: Object.create(null, {
before: {
value: conf.before || MULTIDENTITY
},
after: {
value: conf.after || IDENTITY
}
})
},
_fallback: {
value: null,
writable: true
},
fallback: {
value: function set_fallback(value) {
this._fallback = value;
return this;
}
},
function_table: {
value: []
},
when_t: {
value: get_when_func(WHEN_TYPE)
},
when_v: {
value: get_when_func(WHEN_VALUE)
}
});
return multimethod;
};
return multi;
})();
var repo = {
data: [124,1231,2413423,5234,234,1234,23,42,34,1234,123,452,134,123,42,134,23,4123,512,35,13,24,134],
filter: multi().when_t(String)
(multi().when_v("even")
(function () {
return this.data.filter(function (n) {
return n % 2 === 0;
});
})
.when_v("odd")
(function () {
return this.data.filter(function (n) {
return n % 2 !== 0;
});
}))
.when_v("sort")
(function () {
return this.data.sort(function (a, b) {
return a - b;
})
})
.when_t(String, Number)
(function (s, n) {
return multi().when_v("under")
(function () {
return this.data.filter(function (q) {
return q < n;
})
})
.when_v("over")
(function () {
return this.data.filter(function (q) {
return q > n;
})
}).call(this, s)
})
};
//repo.filter("even");
//repo.filter("odd");
repo.filter();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment