Just looking for some critiques hopefully from haskellers of how I might be breaking monad laws or just monadding all wrong. My or
is something like the mplus
for either
and usable as a catch for failures from then
which behaves mostly like the error monad.
I'm barely a haskeller so this was a big stretch for me, but I think it came out pretty well. Basic goal was some combinators you could create state machines compositionally with like how parser combinators are used to create state machines.
I wonder if my or
might be more like the applicative <|>
and if it could be improved to be less repeating.. does my structure anywhere cause repetition dangers where an entire chain gets executed twice because of one of the pieces composed into it?
(function() {
this.m_ = {};
/* canContinue: error monad gate, and general monad validator */
this.m_.canContinue = function(m) { return m !== undefined && m.m !== undefined && m.m === Success && m.a !== undefined; };
/* functor instance */
this.m_.map = function(m, f) { return m.m === Failure ? new m.m.f(m.a) : new m.m.f(f(m.a)); };
/* applicative instance; ret = return = pure */
this.m_.ret = function(a) { return new success(a); };
this.m_.ap = function(mf, ma) {
return
mf.m === Failure ? new mf.m.f(mf.a) :
(ma.m === Failure ? new ma.m.f(ma.a) :
new success(mf.a(ma.a)));
};
/* alternative instance */
this.m_.alternative = function(f1, f2) {
return function(a) {
var resultf1 = f1(a);
if (resultf1.m === Success) return resultf1;
return f2(a);
};
};
/* monad instance */
this.m_.bind = function(f, m) { return m_.canContinue(m) ? f(m.a) : m; };
this.m_.kleisli = function(f1, f2) { return function(a) { return m_.bind(f2, f1(a)) }; };
/* error monad's monadic actions, and some english verbiage aliases */
this.m_.until = function(f, p) {
return function(a) {
var result = f(a);
var goodResult = result;
while(m_.bind(p, result).m === Failure) {
if (result.m === Failure) return goodResult;
else goodResult = result;
result = m_.bind(f, result);
}
return result;
};
};
this.m_.run = function(f, a) { return m_.bind(f, m_.ret(a)); };
this.m_.or = this.m_.alternative;
this.m_.then = this.m_.kleisli;
(function() {
this.or = function(f2) { return m_.or(this, f2); };
this.then = function(f2) { return m_.then(this, f2); };
this.until = function(p) { return m_.until(this, p); };
}).call(Function.prototype);
var either = function(m, a) { this.m = m; this.a = a; };
var Success = {}
var Failure = {}
Success.f = this.success = function(a) { return new either(Success, a); };
Failure.f = this.failure = function(a) { return new either(Failure, a); };
}).call(this);
///////////////////////////////////////////////////////////////
///////// TEST GARBAGE FOR PONDERING USES BELOW ///////////////
var assertEqual = function(a, b, msg) {
return function(x) { return a === b ? new success(b) : new failure(msg); };
};
var validateCar = function(x) {
return m_.run(
assertEqual(x.color, "red", "wrong color")
.then(assertEqual(x.gas, 20, "wrong gas"))
.then(assertEqual(x.mph, 30, "wrong mph")), 0);
};
m_.run(validateCar, { gas: 20, mph: 30, color: "red" });
var inc = function(a) { return a > -1 ? new success(a+1) : new failure("need positive number"); };
var dec = function(a) { return a < 0 ? new success(a-1) : new failure("need negative number"); };
var stop = function(limit, isDec) { return function(a) {
return isDec ?
(a > limit ? new failure("go") : new success("stop")) :
(a < limit ? new failure("go") : new success("stop")); };
};
var bla = new failure("ah fail");
var test = function(a) {
return typeof a == "string" ? new success(a) : new failure(a);
};
var incOrDecTwice = m_.or(inc, dec).then(m_.or(inc, dec));
m_.run(incOrDecTwice.until(stop(22, true)), 2);
m_.run(incOrDecTwice.then(dec), 4);
m_.run(m_.or(inc, dec), 1);
m_.run(m_.or(inc, dec).until(m_.or(stop(22, false), stop(-22, true))), 1);
m_.run(m_.or(inc, dec).until(m_.or(stop(22, false), stop(-22, true))), -1);
Any thoughts on whether to put or
onto Function.prototype
like then
is? I feel it's scoping is a touch confused there to a reader.. but at the same time it creates a nice symmetry, so I'm on the fence about it...