所以我的问题是:还有其他方法可以
list在Javascript中简洁地实现非确定性monad吗?
是的,您可以使用生成器àla
immutagen在Javascript中简洁地实现非确定性monad,例如list
monad 。但是,由于无法多次从特定位置恢复Javascript中的生成器,因此您必须通过创建和重放多个生成器来模仿此行为。该解决方案具有几个缺点。
- 它的效率很低,因为需要创建并重播多个生成器,从而导致时间复杂度呈二次增长。
- 它仅适用于纯monad和纯计算,因为需要创建并重播多个生成器。因此,副作用将被错误地多次执行。
创建非确定性单子(例如列表单子)所需要的是不可变的生成器。不变生成器可以从特定位置多次恢复。不幸的是,Javascript本身并不支持不可变的生成器。但是,我们可以通过创建和重放多个可变生成器来模拟它。因此,让我们看一下如何创建不可变的生成器。
我们需要解决的第一个问题是将可变生成器重播到特定点的方法。我们使用称为再生器的特殊功能类来实现。再生器是返回可变生成器的任何函数。此类函数的最简单示例是
function*() {}。因此,每个生成器功能都是一个再生器,但不是每个再生器都是一个生成器功能。您可以使用以下功能通过推进旧的再生器来创建新的再生器。
// type Regenerator = Arguments -> MutableGenerator// next :: (Regenerator, Arguments) -> Regeneratorconst next = (regen, ...args) => data => { const gen = regen(...args); return gen.next(data), gen;};
该
next功能可用于将再生器推进到特定点。例如,考虑以下代码片段。
const next = (regen, ...args) => data => { const gen = regen(...args); return gen.next(data), gen;};const regen1 = next(regen0, 1, 2, 3);const regen2 = next(regen1, undefined); // first value of mutable generator ignoredconst regen3 = next(regen2, 10);const gen1 = regen3(20);const gen2 = regen3(30);const result1 = gen1.next(40).value; // 10 + 20 + 40const result2 = gen2.next(50).value; // 10 + 30 + 50console.log(result1, result2);function* regen0(a, b, c) { const x = yield a; const y = yield b; const z = yield c; return x + y + z;}
如您所见,我们可以使用该
next函数推进再生器,或者将再生器应用于值以获得可变的生成器。现在,我们可以将可变的生成器重播到特定的位置,我们可以如下创建不可变的生成器。
// immutagen :: Regenerator -> Arguments -> ImmutableGeneratorconst immutagen = regen => (...args) => function loop(regen) { return (gen, data) => { const {value, done} = gen.next(data); if (done) return {value, next: null}; let replay = false; const recur = loop(next(regen, data)); return {value, next: value => { if (replay) return recur(regen(data), value); replay = true; return recur(gen, value); }}; };}(next(regen, ...args))(regen(...args));
该
immutagen函数可用于创建不可变的生成器函数,我们可以称其为产生不可变的生成器。以下是有关如何创建和使用不可变生成器的示例。
const next = (regen, ...args) => data => { const gen = regen(...args); return gen.next(data), gen;};const immutagen = regen => (...args) => function loop(regen) { return (gen, data) => { const {value, done} = gen.next(data); if (done) return {value, next: null}; let replay = false; const recur = loop(next(regen, data)); return {value, next: value => { if (replay) return recur(regen(data), value); replay = true; return recur(gen, value); }}; };}(next(regen, ...args))(regen(...args));const foo = immutagen(function* (a, b, c) { const x = yield a; const y = yield b; const z = yield c; return x + y + z;});const bar = foo(1, 2, 3).next(10).next(20);const result1 = bar.next(30).value; // 10 + 20 + 30const result2 = bar.next(40).value; // 10 + 20 + 40console.log(result1, result2);
最后,现在我们有了不可变的生成器,我们可以更简洁地实现非确定性单子,例如列表单子,如下所示:
const next = (regen, ...args) => data => { const gen = regen(...args); return gen.next(data), gen;};const immutagen = regen => (...args) => function loop(regen) { return (gen, data) => { const {value, done} = gen.next(data); if (done) return {value, next: null}; let replay = false; const recur = loop(next(regen, data)); return {value, next: value => { if (replay) return recur(regen(data), value); replay = true; return recur(gen, value); }}; };}(next(regen, ...args))(regen(...args));const monad = bind => regen => (...args) => function loop({value, next}) { return next ? bind(value, val => loop(next(val))) : value;}(immutagen(regen)(...args));const flatMap = (array, callback) => array.flatMap(callback);const list = monad(flatMap);const foo = list(function* (xs, ys) { const x = yield xs; const y = yield ys; return [x * y];});console.log(foo([1, 2, 3], [4, 5, 6]));
请注意,该
monad功能仅需要
bind。不需要了
unit。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)