【发布时间】:2013-12-09 01:24:52
【问题描述】:
当我在阅读了数十篇教程后了解了 monad 时,我正在尝试在 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 解释 monads (code) 之后,我明白他提出的 MONAD 只是一个对象,它的方法也可以用原型实现。 unit 方法是构造函数,bind 是一个实例方法,它使用给定的参数对值运行函数。然后lift 向原型添加一个新方法,该方法在单子值上运行给定函数。
但是,它是真正的单子还是单子模式like jQuery?我对 monad 的这种特殊解释有疑问,因为没有计算序列,bind 方法立即运行函数并返回“monad”的新实例,它不是组合,就像我在 LiveScript 中实现的 monad它基于 Haskell 示例。
所以我的问题是:
- 我的 monad 实现是否正确?
- 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)->List([2]).bind((y)->List([3]).bind((z)->List.unit(x+y+z))))。当然你可以为此实现一些糖函数,在这种情况下lift3。 -
我试图收集这些值并创建一个动态绑定函数,然后在
do方法中我将foldl (<<) fns组合它们,但没有工作。我明白你的意思,无论如何你都必须传递函数。我知道使用单个lift函数,我可以在 LiveScript 中简单地执行unit 1 |> lift add 2 |> lift add 3 |> lift add1 #=> [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