Trinithis
07-20-2009, 09:54 PM
I wrote some code to deal with general function (and method) overloading in Javascript. You can overload based on arity, types, optional arguments, and variable length arguments.
Overload Syntax:
var foo = overload (
[Type1, Type2, Type3]
, function (x, y, z) { ... }
, [Type4]
, function (x) { ... }
/* etc */
)
Type Syntax:
Type // match the type
[Type, defaultValue] // try matching Type. Else use default value.
[Type] // variable amount of Type
nullable (Type) // accepts Type or null
[[function (x) { return x.length > 5; }]] // Custom predicate.
All matching is done greedily.
Object will not match primitive numbers, primitive booleans, primitive strings, null, or undefined.
String, Number, Boolean, and Function will match against primitives and objects of the corresponding types.
Varargs are condensed into array format before tossed to corresponding functions.
Custom predicates are executed in try-catch blocks, allowing you to not fuss over whether or not your predicate pulling non-existent attributes and other things.
ANYTYPE will match anything. It will even match null.
overload.js:
var nullable, ANYTYPE;
var overload = (function () {
ANYTYPE = {};
var extend = function (child, parent) {
child.prototype = new parent ();
child.prototype.constructor = child;
};
var combine = function (x, xs) {
if (xs) {
xs.push (x);
}
return xs;
};
var Type = function (constr, nextType) {
this.constr = constr;
this.nextType = nextType;
};
Type.prototype = {
constructor: Type
, match: function (arg) {
if (arg === undefined || arg === null) {
return false;
}
return arg instanceof this.constr;
}
, /* Builds arglist in REVERSE if compatable!!! Else returns false. */
unify: function (args, i) {
return this.match (args [i])
? combine (args [i], this.nextType.unify (args, i + 1))
: null
;
}
};
var EndType = function () {
Type.call (this, null, this);
};
extend (EndType, Type);
EndType.prototype.match = function (arg) {
return false;
};
EndType.prototype.unify = function (args, i) {
return args.length === i
? []
: null
;
};
var ENDTYPE = new EndType ();
var Nullable = function (constr, nextType) {
Type.call (this, constr, nextType);
};
extend (Nullable, Type);
Nullable.prototype.match = function (arg) {
return arg === null || Type.prototype.match.call (this, arg);
};
nullable = function (constr) {
return new Nullable (constr);
};
var AnyType = function (nextType) {
Type.call (this, null, nextType);
}
extend (AnyType, Type);
AnyType.prototype.match = function (arg) {
return true;
};
var Primitive = function (constr, nextType) {
Type.call (this, constr, nextType);
switch (constr) {
case String:
this.typeOf = "string";
break;
case Number:
this.typeOf = "number";
break;
case Boolean:
this.typeOf = "boolean";
break;
case Function:
this.typeOf = "function";
break;
}
};
extend (Primitive, Type);
Primitive.prototype.match = function (arg) {
return typeof arg === this.typeOf || Type.prototype.match.call (this, arg);
};
var Predicate = function (pred, nextType) {
Type.call (this, null, nextType);
this.pred = pred;
}
extend (Predicate, Type);
Predicate.prototype.match = function (arg) {
try {
return this.pred (arg);
}
catch (e) {
return false;
}
};
var TypeWrapper = function (type, nextType) {
Type.call (this, null, nextType);
this.type = type;
};
extend (TypeWrapper, Type);
TypeWrapper.prototype.match = function (arg) {
return this.type.match (arg);
};
var Optional = function (type, nextType, defaultValue) {
TypeWrapper.call (this, type, nextType);
this.defaultValue = defaultValue;
};
extend (Optional, TypeWrapper);
Optional.prototype.unify = function (args, i) {
if (this.match (args [i])) {
return combine (args [i], this.nextType.unify (args, i + 1));
}
return combine (this.defaultValue, this.nextType.unify (args, i));
};
var Variadic = function (type, nextType) {
TypeWrapper.call (this, type, nextType);
};
extend (Variadic, TypeWrapper);
Variadic.prototype.unify = function (args, i) {
var result;
var maxJ = args.length;
do {
var j = i;
var argGroup = [];
while (j < maxJ && this.type.match (args [j])) {
j = argGroup.push (args [j]);
}
maxJ = j - 1;
result = combine (argGroup, this.nextType.unify (args, j));
if (result) {
return result;
}
} while (maxJ >= i);
return result;
};
var parseType = function (sig, nextType) {
if (sig instanceof Array) {
if (sig [0] instanceof Array) {
return new Predicate (sig [0] [0], nextType);
}
else if (sig.length === 1) {
return new Variadic (parseType (sig [0]), nextType);
}
else if (sig.length === 2) {
return new Optional (parseType (sig [0]), nextType, sig [1]);
}
}
else if (sig === ANYTYPE) {
return new AnyType (nextType);
}
else if (sig.constructor === Nullable) {
return new Nullable (sig.constr, nextType);
}
else if (sig === String || sig === Number || sig === Boolean || sig === Function) {
return new Primitive (sig, nextType);
}
else {
return new Type (sig, nextType);
}
};
var mkTypeChain = function (sigs) {
var chain = ENDTYPE;
for (var i = sigs.length - 1; i >= 0; --i) {
chain = parseType (sigs [i], chain);
}
return chain;
};
return function () {
if (arguments.length % 2 !== 0) {
throw new Error ("overload: Invalid number of arguments.");
}
var typeChains = [];
var funcs = [];
for (var i = 0; i < arguments.length; i += 2) {
typeChains.push (mkTypeChain (arguments [i]));
funcs.push (arguments [i + 1]);
}
return function () {
for (var i = 0; i < typeChains.length; ++i) {
var result = typeChains [i].unify (arguments, 0);
if (result) {
result.reverse ();
return funcs [i].apply (this, result);
}
}
};
};
}) ();
Example:
var foo = overload (
[[[function (x) { return x.length == 1; }]]]
, function (chr) {
return [1, chr];
}
, [[Number, 777], String]
, function (num, str) {
return [2, num, str];
}
, [String, String, String]
, function (str1, str2, str3) {
return [3, str1, str2, str3];
}
, [String, String, Number]
, function (str1, str2, num) {
return [4, str1, str2, num];
}
, [[Number]]
, function (nums) {
return [5, nums.toSource ()];
}
, [[Number], Number, String]
, function (nums, num, str) {
return [6, nums.toSource (), num, str];
}
, [Object, Object]
, function (obj1, obj2) {
return [7, obj1, obj2];
}
, [nullable (Object)]
, function (obj) {
return [8, String (obj)];
}
, [ANYTYPE, Object]
, function (obj1, obj2) {
return [9, obj1, obj2];
}
);
document.write (foo ("x"), "<br />"); // 1,x
document.write (foo ("hello"), "<br />"); // 2,777,hello
document.write (foo (888, "hello"), "<br />"); // 2,888,hello
document.write (foo ("a", "b", "c"), "<br />"); // 3,a,b,c
document.write (foo ("a", "b", 666), "<br />"); // 4,a,b,666
document.write (foo (), "<br />"); // 5,[]
document.write (foo (222), "<br />"); // 5,[222]
document.write (foo (22, 33, 44, 55), "<br />");// 5,[22,33,44,55]
document.write (foo (11, 22, 33, 44, "a"), "<br />");//6,[11,22,33],44,a
document.write (foo ({}, /regex/), "<br />"); // 7,[object Object],/regex/
document.write (foo (/regex/), "<br />"); // 8,/regex/
document.write (foo (null), "<br />"); // 8,null
document.write (foo (true, /regex/), "<br />"); // 9,[object Object],/regex/
Overload Syntax:
var foo = overload (
[Type1, Type2, Type3]
, function (x, y, z) { ... }
, [Type4]
, function (x) { ... }
/* etc */
)
Type Syntax:
Type // match the type
[Type, defaultValue] // try matching Type. Else use default value.
[Type] // variable amount of Type
nullable (Type) // accepts Type or null
[[function (x) { return x.length > 5; }]] // Custom predicate.
All matching is done greedily.
Object will not match primitive numbers, primitive booleans, primitive strings, null, or undefined.
String, Number, Boolean, and Function will match against primitives and objects of the corresponding types.
Varargs are condensed into array format before tossed to corresponding functions.
Custom predicates are executed in try-catch blocks, allowing you to not fuss over whether or not your predicate pulling non-existent attributes and other things.
ANYTYPE will match anything. It will even match null.
overload.js:
var nullable, ANYTYPE;
var overload = (function () {
ANYTYPE = {};
var extend = function (child, parent) {
child.prototype = new parent ();
child.prototype.constructor = child;
};
var combine = function (x, xs) {
if (xs) {
xs.push (x);
}
return xs;
};
var Type = function (constr, nextType) {
this.constr = constr;
this.nextType = nextType;
};
Type.prototype = {
constructor: Type
, match: function (arg) {
if (arg === undefined || arg === null) {
return false;
}
return arg instanceof this.constr;
}
, /* Builds arglist in REVERSE if compatable!!! Else returns false. */
unify: function (args, i) {
return this.match (args [i])
? combine (args [i], this.nextType.unify (args, i + 1))
: null
;
}
};
var EndType = function () {
Type.call (this, null, this);
};
extend (EndType, Type);
EndType.prototype.match = function (arg) {
return false;
};
EndType.prototype.unify = function (args, i) {
return args.length === i
? []
: null
;
};
var ENDTYPE = new EndType ();
var Nullable = function (constr, nextType) {
Type.call (this, constr, nextType);
};
extend (Nullable, Type);
Nullable.prototype.match = function (arg) {
return arg === null || Type.prototype.match.call (this, arg);
};
nullable = function (constr) {
return new Nullable (constr);
};
var AnyType = function (nextType) {
Type.call (this, null, nextType);
}
extend (AnyType, Type);
AnyType.prototype.match = function (arg) {
return true;
};
var Primitive = function (constr, nextType) {
Type.call (this, constr, nextType);
switch (constr) {
case String:
this.typeOf = "string";
break;
case Number:
this.typeOf = "number";
break;
case Boolean:
this.typeOf = "boolean";
break;
case Function:
this.typeOf = "function";
break;
}
};
extend (Primitive, Type);
Primitive.prototype.match = function (arg) {
return typeof arg === this.typeOf || Type.prototype.match.call (this, arg);
};
var Predicate = function (pred, nextType) {
Type.call (this, null, nextType);
this.pred = pred;
}
extend (Predicate, Type);
Predicate.prototype.match = function (arg) {
try {
return this.pred (arg);
}
catch (e) {
return false;
}
};
var TypeWrapper = function (type, nextType) {
Type.call (this, null, nextType);
this.type = type;
};
extend (TypeWrapper, Type);
TypeWrapper.prototype.match = function (arg) {
return this.type.match (arg);
};
var Optional = function (type, nextType, defaultValue) {
TypeWrapper.call (this, type, nextType);
this.defaultValue = defaultValue;
};
extend (Optional, TypeWrapper);
Optional.prototype.unify = function (args, i) {
if (this.match (args [i])) {
return combine (args [i], this.nextType.unify (args, i + 1));
}
return combine (this.defaultValue, this.nextType.unify (args, i));
};
var Variadic = function (type, nextType) {
TypeWrapper.call (this, type, nextType);
};
extend (Variadic, TypeWrapper);
Variadic.prototype.unify = function (args, i) {
var result;
var maxJ = args.length;
do {
var j = i;
var argGroup = [];
while (j < maxJ && this.type.match (args [j])) {
j = argGroup.push (args [j]);
}
maxJ = j - 1;
result = combine (argGroup, this.nextType.unify (args, j));
if (result) {
return result;
}
} while (maxJ >= i);
return result;
};
var parseType = function (sig, nextType) {
if (sig instanceof Array) {
if (sig [0] instanceof Array) {
return new Predicate (sig [0] [0], nextType);
}
else if (sig.length === 1) {
return new Variadic (parseType (sig [0]), nextType);
}
else if (sig.length === 2) {
return new Optional (parseType (sig [0]), nextType, sig [1]);
}
}
else if (sig === ANYTYPE) {
return new AnyType (nextType);
}
else if (sig.constructor === Nullable) {
return new Nullable (sig.constr, nextType);
}
else if (sig === String || sig === Number || sig === Boolean || sig === Function) {
return new Primitive (sig, nextType);
}
else {
return new Type (sig, nextType);
}
};
var mkTypeChain = function (sigs) {
var chain = ENDTYPE;
for (var i = sigs.length - 1; i >= 0; --i) {
chain = parseType (sigs [i], chain);
}
return chain;
};
return function () {
if (arguments.length % 2 !== 0) {
throw new Error ("overload: Invalid number of arguments.");
}
var typeChains = [];
var funcs = [];
for (var i = 0; i < arguments.length; i += 2) {
typeChains.push (mkTypeChain (arguments [i]));
funcs.push (arguments [i + 1]);
}
return function () {
for (var i = 0; i < typeChains.length; ++i) {
var result = typeChains [i].unify (arguments, 0);
if (result) {
result.reverse ();
return funcs [i].apply (this, result);
}
}
};
};
}) ();
Example:
var foo = overload (
[[[function (x) { return x.length == 1; }]]]
, function (chr) {
return [1, chr];
}
, [[Number, 777], String]
, function (num, str) {
return [2, num, str];
}
, [String, String, String]
, function (str1, str2, str3) {
return [3, str1, str2, str3];
}
, [String, String, Number]
, function (str1, str2, num) {
return [4, str1, str2, num];
}
, [[Number]]
, function (nums) {
return [5, nums.toSource ()];
}
, [[Number], Number, String]
, function (nums, num, str) {
return [6, nums.toSource (), num, str];
}
, [Object, Object]
, function (obj1, obj2) {
return [7, obj1, obj2];
}
, [nullable (Object)]
, function (obj) {
return [8, String (obj)];
}
, [ANYTYPE, Object]
, function (obj1, obj2) {
return [9, obj1, obj2];
}
);
document.write (foo ("x"), "<br />"); // 1,x
document.write (foo ("hello"), "<br />"); // 2,777,hello
document.write (foo (888, "hello"), "<br />"); // 2,888,hello
document.write (foo ("a", "b", "c"), "<br />"); // 3,a,b,c
document.write (foo ("a", "b", 666), "<br />"); // 4,a,b,666
document.write (foo (), "<br />"); // 5,[]
document.write (foo (222), "<br />"); // 5,[222]
document.write (foo (22, 33, 44, 55), "<br />");// 5,[22,33,44,55]
document.write (foo (11, 22, 33, 44, "a"), "<br />");//6,[11,22,33],44,a
document.write (foo ({}, /regex/), "<br />"); // 7,[object Object],/regex/
document.write (foo (/regex/), "<br />"); // 8,/regex/
document.write (foo (null), "<br />"); // 8,null
document.write (foo (true, /regex/), "<br />"); // 9,[object Object],/regex/