Monad模式在接收器上

时间:2013-11-24 11:47:25

标签: javascript monads livescript

在阅读了几十个教程之后我学习了monads,我正在尝试用JavaScript实现这个模式。我正在使用LiveScriptPrelude来翻译一些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示例。

所以我的问题是:

  1. 我的monad实现是否正确?
  2. monad的Crockford's implementation是否正确?

1 个答案:

答案 0 :(得分:3)

  

我的monad实现是否正确?

是的,您的unitbind函数执行了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。