现在node.js支持ECMAScript Harmony生成器,我们可以像Haskell中的do
块一样简洁地编写单子代码:
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
是一个函数,可用于创建像以下这样的确定性单子:
var maybe = monad(function (a) {
return {just: a};
}, function (m, f) {
return m === null ? null : f(m.just);
});
你现在可以使用maybe
,如下所示:
var readZip = maybe(function * (a, b) {
var a = yield readList(a);
var b = yield readList(b);
return _.zip(a, b);
});
上述函数readZip
接受两个字符串,将它们转换为列表,然后将它们压缩。如果有错误,则会立即返回null
。它依赖于以下函数:
function readList(string) {
try {
var value = JSON.parse(string);
return value instanceof Array ? {just: value} : null;
} catch (error) {
return null;
}
}
我们进行测试以检查它是否按预期工作:
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
同样地,我们可以创建任何其他确定性单子。例如,我最喜欢的 cont
单子:
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
来简洁地创建以续传方式编写的函数:
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。它不适用于非确定性 monad,如 list
monad,因为你只能从特定位置恢复生成器一次。
我的问题是:是否有其他简洁的方法在 JavaScript 中实现非确定性 monad,例如 list
monad?