Skip to content

Instantly share code, notes, and snippets.

@raganwald
Last active August 13, 2019 11:20
Show Gist options
  • Save raganwald/c573a4f6ac29ab0d60d8 to your computer and use it in GitHub Desktop.
Save raganwald/c573a4f6ac29ab0d60d8 to your computer and use it in GitHub Desktop.
Multiple dispatch in JavaScript
function isType (type) {
return function (arg) {
return typeof(arg) === type;
};
}
function instanceOf (clazz) {
return function (arg) {
return arg instanceof clazz;
};
}
function isPrototypeOf (proto) {
return Object.prototype.isPrototypeOf.bind(proto);
}
function MatchTypes () {
var matchers = [].slice.call(arguments, 0, arguments.length - 1),
body = arguments[arguments.length - 1];
function typeChecked () {
var i,
arg,
value;
if (arguments.length != matchers.length) return;
for (i in arguments) {
arg = arguments[i];
if (!matchers[i].call(this, arg)) return;
}
value = body.apply(this, arguments);
return value === undefined
? null
: value;
}
return imitate(body, typeChecked);
}
////////// EXAMPLE //////////
function Fighter () {};
function Meteor () {};
var handlesManyCases = Match(
MatchTypes(
instanceOf(Fighter), instanceOf(Meteor),
function (fighter, meteor) {
return "a fighter has hit a meteor";
}
),
MatchTypes(
instanceOf(Fighter), instanceOf(Fighter),
function (fighter, fighter) {
return "a fighter has hit another fighter";
}
),
MatchTypes(
instanceOf(Meteor), instanceOf(Fighter),
function (meteor, fighters) {
return "a meteor has hit a fighter";
}
),
MatchTypes(
instanceOf(Meteor), instanceOf(Meteor),
function (meteor, meteor) {
return "a meteor has hit another meteor";
}
)
);
handlesManyCases(new Meteor(), new Meteor());
//=> 'a meteor has hit another meteor'
handlesManyCases(new Fighter(), new Meteor());
//=> 'a fighter has hit a meteor'
////////// Multiple Dispatch for Methods //////////
var FighterPrototype = {},
MeteorPrototype = {};
FighterPrototype.crashInto = Match(
MatchTypes(
isPrototypeOf(FighterPrototype),
function (fighter) {
return "fighter(fighter)";
}
),
MatchTypes(
isPrototypeOf(MeteorPrototype),
function (fighter) {
return "fighter(meteor)";
}
)
);
MeteorPrototype.crashInto = Match(
MatchTypes(
isPrototypeOf(FighterPrototype),
function (fighter) {
return "meteor(fighter)";
}
),
MatchTypes(
isPrototypeOf(MeteorPrototype),
function (meteor) {
return "meteor(meteor)";
}
)
);
var someFighter = Object.create(FighterPrototype),
someMeteor = Object.create(MeteorPrototype);
someFighter.crashInto(someMeteor);
//=> 'fighter(meteor)'
function nameAndLength(name, length, body) {
var abcs = [ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',
'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',
'z', 'x', 'c', 'v', 'b', 'n', 'm' ],
pars = abcs.slice(0, length),
src = "(function " + name + " (" + pars.join(',') + ") { return body.apply(this, arguments); })";
return eval(src);
}
function imitate(exemplar, body) {
return nameAndLength(exemplar.name, exemplar.length, body);
}
function getWith (prop, obj) {
function gets (obj) {
return obj[prop];
}
return obj === undefined
? gets
: gets(obj);
}
function mapWith (fn, mappable) {
function maps (collection) {
return collection.map(fn);
}
return mappable === undefined
? maps
: maps(collection);
}
function pluckWith (prop, collection) {
var plucker = mapWith(getWith(prop));
return collection === undefined
? plucker
: plucker(collection);
}
function Match () {
var fns = [].slice.call(arguments, 0),
lengths = pluckWith('length', fns),
length = Math.min.apply(null, lengths),
names = pluckWith('name', fns).filter(function (name) { return name !== ''; }),
name = names.length === 0
? ''
: names[0];
return nameAndLength(name, length, function () {
var i,
value;
for (i in fns) {
value = fns[i].apply(this, arguments);
if (value !== undefined) return value;
}
});
}
@raganwald
Copy link
Author

Extracted from JavaScript Spessore.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment