在阅读了几十个教程之后我学习了monads,我正在尝试用JavaScript实现这个模式。我正在使用LiveScript和Prelude来翻译一些Haskell monad示例。
所以我试图实现的monad是List monad。我在LiveScript中写了以下内容:
List = do ->
# unit :: a -> ma
unit = (a) -> [a]
# bind :: ma -> (a -> mb) -> mb
bind = (ma, f) --> concat (map f) ma
# lift :: (a -> b) -> ma -> mb
lift = (f, ma) --> bind ma, (a) -> unit f a
{unit, bind, lift}
add1 = (x) -> x+1
let {unit, bind} = List
x <- bind [1]
y <- bind [2]
z <- bind [3]
unit add1 x+y+z #=> [7]
(List.lift add1) [1 2 3] #=> [2 3 4]
在相同的缩进级别嵌套函数的LiveScript语法非常方便,但它显然转化为JavaScript回调地狱:
List = function(){
var unit, bind, lift;
unit = function(a){
return [a];
};
bind = curry$(function(ma, f){
return concat(map(f)(ma));
});
lift = curry$(function(f, ma){
return bind(ma, function(a){
return unit(f(a));
});
});
return { unit: unit, bind: bind, lift: lift };
}();
add1 = function(x){
return x + 1;
};
(function(arg$){
var unit, bind;
unit = arg$.unit, bind = arg$.bind;
bind([1], function(x){
return bind([2], function(y){
return bind([3], function(z){
return unit(add1(x + y + z));
});
});
});
}.call(this, List));
List.lift(add1)([1, 2, 3]);
我想要的是在接收器上实现模式,以便能够像这样使用它:
List([1]).bind([2]).bind([3]).do(function(x,y,z){ x+y+z });
在观看this video Crockford在JavaScript(code)中解释monad之后,我明白他提出的MONAD
只是一个方法,其中的方法也可以用原型来实现。 unit
方法是构造函数,bind
是一个实例方法,它使用给定的参数对值运行函数。然后lift
向原型添加一个新方法,该方法在monadic值上运行给定函数。
但是,它是一个真正的monad还是monadic模式like jQuery?我对monad的这种特殊解释有疑问,因为没有计算序列,bind
方法立即运行函数并返回“monad”的新实例,它不是组合,就像我实现的monad一样在LiveScript中,它基于Haskell示例。
所以我的问题是:
答案 0 :(得分:3)
我的monad实现是否正确?
是的,您的unit
和bind
函数执行了List monad所期望的功能。
然而,第二行List([1]).bind([2]).bind([3]).do(function(x,y,z){ x+y+z });
看起来并不像monad。
那些LiveScript回叫,就像Haskell的记号一样,只是必要的lift
回调地狱的语法糖。你仍然需要写:
List([1]).bind((x)->List([2]).bind((y)->List([3]).bind((z)->List.unit(x+y+z))))
如果以bind
表示,它将永远是“混乱”。为了使它更好(并且更高效),你将使用列表理解:
concat(for x in [1] for y in [2] for z in [3]: x+y+z)
另一个想法是:由于JavaScript的松散输入,如果你愿意,应该可以用变量参数实现一个真正的通用(递归?)liftN
函数:
function lift(n, fn) {
var argsPos = 2;
if (typeof n != "number") {
fn = n;
n = fn.length;
argsPos--;
}
var args = [].slice.call(arguments, argsPos);
if (n < args.length) // curry
return function(){
return lift.apply(null, [n, fn].concat(args, [].slice.call(arguments)));
}
return (function bindr(bound, args)
if (!args.length)
return unit(fn.apply(null, bound));
return bind(args[0], function(a) {
return bindr([x].concat(bound), args.slice(1));
});
})([], args);
}
如果你想使用更加面向对象的模式,那么你可以将单个组合映射到最后可以应用的参数元组:
List([1]).nest(List([2])).nest(List([3])).do(function(x,y,z){ x+y+z })
// where
Monad.prototype.map = function(fn) {
var unit = this.constructor; // ???
return this.bind(function(x)
return unit(fn(x));
});
};
Monad.prototype.nest = function(m2) {
return this.map(function(x) {
return m2.map(function(y)
return [x, y]; // tuple
});
});
});
Monad.prototype.do = function(fn, n) {
function flatten(n, t) {
return n<=1 ? [t] : flatten(n-1, t[0]).concat([t[1]]);
}
return this.map(function(ts) {
return fn.apply(null, flatten(n || fn.length, ts));
});
};
Crockford对monad的实现是否正确?
也许。他确实实现了idendity monad,但代码看起来好像是想通过覆盖bind
方法将其扩展到其他monad,这可能不适用于所有monad。