Created
September 6, 2012 00:55
-
-
Save puffnfresh/3649166 to your computer and use it in GitHub Desktop.
Operator overloading in JavaScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This file shows a hack to achieve operator overloading in | |
// JavaScript. I have defined 3 different operators: | |
// | |
// >= = monadic bind | |
// >> = kleisli composition | |
// > = functor map | |
// * = applicative apply | |
// + = semigroup append | |
// | |
// Head straight to the bottom to see example usages. | |
// Gross mutable global | |
var queue; | |
// Boilerplate | |
function Do() { | |
if(arguments.length) | |
throw new TypeError("Arguments given to Do. Proper usage: Do()(arguments)"); | |
var oldQueue = queue; | |
queue = []; | |
return function(n) { | |
var op, x, i; | |
if(!queue.length) { | |
queue = oldQueue; | |
return n; | |
} | |
if(n === true) op = '>='; | |
if(n === false) op = '>'; | |
if(n === 0) op = '>>'; | |
if(n === 1) op = '*'; | |
if(n === queue.length) op = '+'; | |
if(!op) { | |
queue = oldQueue; | |
throw new Error("Couldn't determine Do operation. Could be ambiguous."); | |
} | |
x = queue[0]; | |
for(i = 1; i < queue.length; i++) { | |
x = x[op](queue[i]); | |
} | |
queue = oldQueue; | |
return x; | |
}; | |
} | |
Do.setValueOf = function(proto) { | |
var oldValueOf = proto.valueOf; | |
proto.valueOf = function() { | |
if(queue === undefined) return oldValueOf.call(this); | |
queue.push(this); | |
return 1; | |
}; | |
}; | |
Do.setValueOf(Function.prototype); | |
Function.prototype['>>'] = function(g) { | |
var f = this; | |
return function(x) { | |
return f(x)['>='](g); | |
}; | |
}; | |
Number.prototype['+'] = function(n) { | |
return this + n; | |
}; | |
// Option | |
function some(x) { | |
if(!(this instanceof some)) return new some(x); | |
this.x = x; | |
this.getOrElse = function() { | |
return x; | |
}; | |
this.map = function(f) { | |
return some(f(x)); | |
}; | |
this['>='] = function(f) { | |
return f(x); | |
}; | |
this['>'] = function(f) { | |
return this.map(f); | |
}; | |
this['*'] = function(s) { | |
return s.map(x); | |
}; | |
this['+'] = function(s) { | |
return some(this.x['+'](s.x)); | |
}; | |
Do.setValueOf(this); | |
} | |
var none = { | |
getOrElse: function(x) { | |
return x; | |
}, | |
map: function() { | |
return this; | |
}, | |
'>=': function() { | |
return this; | |
}, | |
'>': function() { | |
return this; | |
}, | |
'*': function() { | |
return this; | |
}, | |
'+': function() { | |
return this; | |
} | |
}; | |
Do.setValueOf(none); | |
// Example functions | |
function curriedMultiply(x) { | |
return function(y) { | |
return x * y; | |
}; | |
} | |
var multiplyBy14 = curriedMultiply(14); | |
function validateNumber(x) { | |
return x < 0 ? none : some(multiplyBy14(x)); | |
} | |
function getAnswer(x) { | |
return x != 42 ? none : some("You got the answer!"); | |
} | |
// Monad example | |
var monad = Do()( | |
some(3) >= validateNumber >= getAnswer | |
); | |
console.log(monad.getOrElse("Something went wrong")); | |
// Kleisli example | |
var kleisli = Do()( | |
validateNumber >> getAnswer | |
); | |
console.log(kleisli(3).getOrElse("Something went wrong")); | |
// Functor example | |
var functor = Do()( | |
some(3) > multiplyBy14 | |
); | |
console.log(functor.getOrElse(0)); | |
// Applicative example | |
var applicative = Do()( | |
some(curriedMultiply) * some(7) * some(6) | |
); | |
console.log(applicative.getOrElse(0)); | |
// Semigroup example | |
var semigroup = Do()( | |
some(3) + some(39) | |
); | |
console.log(semigroup.getOrElse(0)); | |
// Nested example | |
var nested = Do()( | |
some(function(x) { | |
return x / 6; | |
}) * Do()( | |
Do()( | |
some(3) + some(33) | |
) >= function(x) { | |
return x < 0 ? none : some(x * 7); | |
} | |
) | |
); | |
console.log(nested.getOrElse(0)); | |
// Still works | |
var normal = Do()( | |
38 + 4 | |
); | |
console.log(normal); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment