【问题标题】:Monad pattern on receiver接收器上的单子模式
【发布时间】:2013-12-09 01:24:52
【问题描述】:

当我在阅读了数十篇教程后了解了 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 });

看了this video Crockford 用 Ja​​vaScript 解释 monads (code) 之后,我明白他提出的 MONAD 只是一个对象,它的方法也可以用原型实现。 unit 方法是构造函数,bind 是一个实例方法,它使用给定的参数对值运行函数。然后lift 向原型添加一个新方法,该方法在单子值上运行给定函数。

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

所以我的问题是:

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

【问题讨论】:

  • 我不确定List([1]).bind([2]).bind([3]).do(function(x,y,z){ x+y+z }); 应该做什么。 bind 通常会接受一个函数(产生一个列表)?
  • 我想这是我还不太明白的部分,是如何让 monad 在接收器上工作但仍然使用函数组合。那么我的例子会是什么样子呢?
  • 那里有三个 monad 实例,所以这并不容易。就像 Haskells do-notation 一样,这些 LiveScript 回调只是 必要 lift 回调地狱的语法糖。你仍然需要写List([1]).bind((x)-&gt;List([2]).bind((y)-&gt;List([3]).bind((z)-&gt;List.unit(x+y+z))))。当然你可以为此实现一些糖函数,在这种情况下lift3
  • 我试图收集这些值并创建一个动态绑定函数,然后在do 方法中我将foldl (&lt;&lt;) fns 组合它们,但没有工作。我明白你的意思,无论如何你都必须传递函数。我知道使用单个 lift 函数,我可以在 LiveScript 中简单地执行 unit 1 |&gt; lift add 2 |&gt; lift add 3 |&gt; lift add1 #=&gt; [7],但它在 JS 中仍然看起来像一团糟。所以我明白函数必须是一个参数,我必须根据我有多少实例创建许多liftX
  • 是的,如果用lift 表示,它在 JS 中永远是一个“混乱”。为了使其更好(并且性能更高),您可以使用理解:concat(for x in [1] for y in [2] for z in [3]: x+y+z)。另一个想法:由于 JavaScript 的输入松散,如果你愿意,应该可以使用可变参数实现真正的泛型(递归?)liftN 函数。

标签: javascript monads livescript


【解决方案1】:

我的 monad 实现是否正确?

是的,您的 unitbind 函数执行 List monad 所期望的功能。

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

那些 LiveScript 回调,就像 Haskell 的 do-notation 一样,只是必要的 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-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-28
    • 1970-01-01
    • 2015-04-04
    • 2021-10-03
    • 2018-08-07
    相关资源
    最近更新 更多