Skip to content

Instantly share code, notes, and snippets.

@puffnfresh
Created September 6, 2012 00:55
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save puffnfresh/3649166 to your computer and use it in GitHub Desktop.
Save puffnfresh/3649166 to your computer and use it in GitHub Desktop.
Operator overloading in JavaScript
// 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