在JavaScript中实现monad

现在, node.js支持ECMAScript Harmony生成器,我们可以在Haskell中简洁地写出monadic代码:

 function monad(unit, bind) { return function (f) { return function () { var g = f.apply(this, arguments); return typeOf(g) === "Generator" ? send() : unit(g); function send(value) { var result = g.next(value); if (result.done) return unit(result.value); else return bind(result.value, send); } }; }; } function typeOf(value) { return Object.prototype.toString.call(value).slice(8, -1); } 

在上面的代码中, monad是一个可用于创build确定性 monad的函数,如下所示:

 var maybe = monad(function (a) { return {just: a}; }, function (m, f) { return m === null ? null : f(m.just); }); 

你现在可能使用如下:

 var readZip = maybe(function * (a, b) { var a = yield readList(a); var b = yield readList(b); return _.zip(a, b); }); 

上面的函数readZip需要两个string,将它们转换成列表,然后将它们readZip 。 如果有错误,则立即返回null 。 这取决于以下function:

 function readList(string) { try { var value = JSON.parse(string); return value instanceof Array ? {just: value} : null; } catch (error) { return null; } } 

我们testing它是否符合预期:

 console.log(readZip('[1,2,3,4]', '["a","b"]')); // [[1,"a"],[2,"b"],[3,"c"]] console.log(readZip('hello', '["a","b"]')); // null console.log(readZip('[1,2,3,4]', 'world')); // null 

同样,我们可以创build任何其他确定性monad。 举个例子,我最喜欢的是monad:

 var cont = monad(function (a) { return function (k) { return k(a); }; }, function (m, k) { return function (c) { return m(function (a) { return k(a)(c); }); }; }); 

现在我们可以简单地使用cont创build连续传递函数:

 var fib = cont(function * (n) { switch (n) { case 0: return 0; case 1: return 1; default: var x = yield fib(n - 1); var y = yield fib(n - 2); return x + y; } }); 

你可以使用fib函数如下:

 fib(10)(function (a) { console.log(a); }); // 55 

不幸的是, monad只能用于确定性monad。 它不适用于list monad等非确定性monad,因为您只能从特定位置恢复一次发生器。

所以我的问题是这样的:是否有任何其他方式来实现非确定性单子,如list monad简洁的JavaScript?

所以我的问题是这样的:是否有任何其他方式来实现非确定性单子,如列表monad简洁的JavaScript?

我build议这个monad实现,我在这里适用于各种monad:

 var extend = function(a, b) { for (var i in b) a[i] = b[i]; return a; }; // Chain a new `this` var fluent = function(f) { return function() { var clone = extend(Object.create(null), this); f.apply(clone, arguments); return clone; }; }; var toArray = function(x) { return Array.prototype.slice.call(x); }; var List = { unit: fluent(function() { this.x = toArray(arguments); }), bind: function(f) { var fx = this.x.map(f.bind(this)); var a = fx[0]; for (var i=1; i<fx.length; i++) ax = axconcat(fx[i].x); return a; }, lift: function(f) { return function(x) { return List.unit(f(x)); } }, valueOf: function() { return this.x; } }; var add1 = function(x) { return x + 1; }; // Laws var m = List.unit(3); var f = List.lift(add1); var laws = [ m.bind(f)[0] == f(3)[0], m.bind(function(x){ return List.unit(x) })[0] == m[0], m.bind(function(x){ return f(x).bind(f) })[0] == m.bind(f).bind(f)[0] ]; console.log(laws); //=> [true, true, true] // lift var result = List.unit(1,2).bind(List.lift(add1)); //=> [2,3] console.log(result.valueOf()); // do var result = List.unit(1,2).bind(function(x) { return this.unit(3,4).bind(function(y) { return this.unit(x + y); }); }); console.log(result.valueOf()); //=> [4,5,5,6] 

显然,“do”语法导致callback地狱,但在LiveScript中,您可以缓解这种痛苦:

 result = do x <- List.unit 1 2 .bind y <- @unit 3 4 .bind @unit x + y 

你也可以创造性地命名你的bind方法:

 result = do x <- List.unit 1 2 .\>= y <- @unit 3 4 .\>= @unit x + y 

似乎有一个很好的方式来实现像这样的monad列表:

 function* unit(value) { yield value; } function* bind(list, transform) { for (var item of list) { yield* transform(item); } } var result = bind(['a', 'b', 'c'], function (element) { return bind([1, 2, 3], function* (element2) { yield element + element2; }); }); for (var item of result) { console.log(item); } 

基于https://curiosity-driven.org/monads-in-javascript#list