2

当我在阅读了几十篇教程后了解了 monad 时,我正在尝试在 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 });

在观看了 Crockford 用 Ja​​vaScript(代码)解释 monad 的视频后,我了解到他提出的只是一个对象,其方法也可以用原型实现。该方法是构造函数,是一个实例方法,它使用给定的参数对值运行函数。然后向原型添加一个新方法,该方法在单值上运行给定函数。MONADunitbindlift

但是,它是真正的单子还是像 jQuery 这样的单子模式?我对 monad 的这种特殊解释有疑问,因为没有计算序列,该bind方法立即运行函数并返回“monad”的新实例,它不是组合,就像我在 LiveScript 中实现的基于关于 Haskell 的例子。

所以我的问题是:

  1. 我对 monad 的实现是否正确?
  2. Crockford对 monad 的实现是否正确?
4

1 回答 1

3

我对 monad 的实现是否正确?

是的,你的unitbind函数做了 List monad 所期望的。

但是,第二行List([1]).bind([2]).bind([3]).do(function(x,y,z){ x+y+z });看起来一点也不像 monad。

就像 Haskell 的 do-notation 一样,这些 LiveScript 回调只是必要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 的实现是否正确?

也许。他确实实现了 identity monad,但代码看起来好像他想通过覆盖该bind方法将其扩展到其他 monad,这可能不适用于所有 monad。

于 2013-11-26T00:21:37.847 回答