在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); }