如何为continuation monad实现stack-safe chainRec操作符?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何为continuation monad实现stack-safe chainRec操作符?相关的知识,希望对你有一定的参考价值。
我目前正在试验延续monad。 Cont
实际上在javascript中很有用,因为它从回调模式中抽象出来。
当我们处理monadic递归时,总是存在堆栈溢出的风险,因为递归调用不在尾部位置:
const chain = g => f => k =>
g(x => f(x) (k));
const of = x => k =>
k(x);
const id = x =>
x;
const inc = x =>
x + 1;
const repeat = n => f => x =>
n === 0
? of(x)
: chain(of(f(x))) (repeat(n - 1) (f));
console.log(
repeat(1e6) (inc) (0) (id) // stack overflow
);
但是,即使我们能够将某些情况转换为尾递归,我们仍然注定要失败,因为Javascript没有TCO。因此,我们必须在某个时刻回到循环。
puresrcipt有一个MonadRec
类型类,带有tailRecM
运算符,可以为某些monad进行尾递归monadic计算。所以我尝试在Javascript中实现chainRec
主要是根据幻想的土地规范:
const chain = g => f => k => g(x => f(x) (k));
const of = x => k => k(x);
const id = x => x;
const Loop = x =>
({value: x, done: false});
const Done = x =>
({value: x, done: true});
const chainRec = f => x => {
let step = f(Loop, Done, x);
while (!step.done) {
step = f(Loop, Done, step.value);
}
return of(step.value);
};
const repeat_ = n => f => x =>
chainRec((Loop, Done, [n, x]) => n === 0 ? Done(x) : Loop([n - 1, f(x)])) ([n, x]);
console.log(
repeat_(1e6) (n => n + 1) (0) (id) // 1000000
);
这有效,但它看起来很像作弊,因为它似乎绕过了monadic链接,从而绕过了Cont
的背景。在这种情况下,上下文只是“计算的其余部分”,即。反向的函数组成,结果返回预期值。但它对任何单子都有效吗?
为了清楚我的意思,请查看以下outstanding answer的代码片段:
const Bounce = (f,x) => ({ isBounce: true, f, x })
const Cont = f => ({
_runCont: f,
chain: g =>
Cont(k =>
Bounce(f, x =>
Bounce(g(x)._runCont, k)))
})
// ...
const repeat = n => f => x => {
const aux = (n,x) =>
n === 0 ? Cont.of(x) : Cont.of(f(x)).chain(x => aux(n - 1, x))
return runCont(aux(n,x), x => x)
}
这里chain
以某种方式结合到递归算法中,即可以发生monadic效应。不幸的是,我无法破译此运算符或将其与堆栈不安全版本协调(但Bounce(g(x)._runCont, k)
似乎是f(x) (k)
部分)。
最后,我的问题是,如果我搞砸了chainRec
or的实施,误解了FL规范或者两者都没有,或者都没有?
[编辑]
通过从不同的角度看问题并且值得接受,两个给出的答案都非常有用。因为我只能接受一个 - 嘿stackoverflow,世界并不那么简单! - 我不接受任何。
我是否搞砸了
chainRec
的实施,或误解了FantasyLand规格,或两者兼而有之?
可能是两者,或者至少是第一部分。请注意the type should be
chainRec :: ChainRec m => ((a -> c, b -> c, a) -> m c, a) -> m b
其中m
是Cont
和c
是你的完成/循环包装在a
或b
:
chainRec :: ((a -> DL a b, b -> DL a b, a) -> Cont (DL a b), a) -> Cont b
但是你的chainRec
和repeat
实现根本不使用连续性!
如果我们只实现那种类型,而不需要它应该需要恒定的堆栈空间,它看起来就像
const chainRec = f => x => k =>
f(Loop, Done, x)(step =>
step.done
? k(step.value) // of(step.value)(k)
: chainRec(f)(step.value)(k)
);
或者如果我们甚至放弃懒惰要求(类似于将chain
从g => f => k => g(x => f(x)(k))
转换为g => f => g(f)
(即g => f => k => g(x => f(x))(k)
)),它看起来像
const chainRec = f => x =>
f(Loop, Done, x)(step =>
step.done
? of(step.value)
: chainRec(f)(step.value)
);
甚至掉落Done / Loop
const join = chain(id);
const chainRec = f => x => join(f(chainRec(f), of, x));
(我希望我不会因此而走得太远,但它完美呈现了ChainRec
背后的想法)
有了懒惰的延续和非递归的蹦床,我们会写
const chainRec = f => x => k => {
let step = Loop(x);
do {
step = f(Loop, Done, step.value)(id);
// ^^^^ unwrap Cont
} while (!step.done)
return k(step.value); // of(step.value)(k)
};
循环语法(初始化step
与f
调用,do/while
而不是do
)并不重要,你的也很好,但重要的是f(Loop, Done, v)
返回一个延续。
我将把repeat
的实现作为练习留给读者:D
(提示:如果你有重复的函数f
已经使用continuation,它可能会变得更有用,也更容易正确)
最好的祝福,
我想这可能就是你要找的东西,
const chainRec = f => x =>
f ( chainRec (f)
, of
, x
)
实现repeat
就像你拥有它一样 - 有两个例外(感谢@Bergi来捕捉这个细节)。 1,loop
和done
是链接函数,因此chainRec
回调必须返回延续。 2,我们必须使用run
标记一个函数,所以cont
知道什么时候我们可以安全地折叠挂起的延续堆栈 - 粗体更改
const repeat_ = n => f => x =>
chainRec
((loop, done, [n, x]) =>
n === 0
? of (x) (done) // cont chain done
: of ([ n - 1, f (x) ]) (loop) // cont chain loop
([ n, x ])
const repeat = n => f => x =>
repeat_ (n) (f) (x) (run (identity))
但是,如果你像我们这里一样使用chainRec
,当然没有理由定义中间repeat_
。我们可以直接定义repeat
const repeat = n => f => x =>
chainRec
((loop, done, [n, x]) =>
n === 0
? of (x) (done)
: of ([ n - 1, f (x) ]) (loop)
([ n, x ])
(run (identity))
现在它可以工作,你只需要一个堆栈安全的延续monad - cont (f)
构建一个延续,等待行动g
。如果g
用run
标记,那么它是时候在trampoline
上反弹了。否则构造函数一个新的延续,为call
和f
添加连续的g
// not actually stack-safe; we fix this below
const cont = f => g =>
is (run, g)
? trampoline (f (g))
: cont (k =>
call (f, x =>
call (g (x), k)))
const of = x =>
cont (k => k (x))
在我们进一步讨论之前,我们将验证一切正常
const TAG =
Symbol ()
const tag = (t, x) =>
Object.assign (x, { [TAG]: t })
const is = (t, x) =>
x && x [TAG] === t
// ----------------------------------------
const cont = f => g =>
is (run, g)
? trampoline (f (g))
: cont (k =>
call (f, x =>
call (g (x), k)))
const of = x =>
cont (k => k (x))
const chainRec = f => x =>
f ( chainRec (f)
, of
, x
)
const run = x =>
tag (run, x)
const call = (f, x) =>
tag (call, { f, x })
const trampoline = t =>
{
let acc = t
while (is (call, acc))
acc = acc.f (acc.x)
return acc
}
// ----------------------------------------
const identity = x =>
x
const inc = x =>
x + 1
const repeat = n => f => x =>
chainRec
((loop, done, [n, x]) =>
n === 0
? of (x) (done)
: of ([ n - 1, f (x) ]) (loop))
([ n, x ])
(run (identity))
console.log (repeat (1e3) (inc) (0))
// 1000
console.log (repeat (1e6) (inc) (0))
// Error: Uncaught RangeError: Maximum call stack size exceeded
以上是关于如何为continuation monad实现stack-safe chainRec操作符?的主要内容,如果未能解决你的问题,请参考以下文章
如何为我使用 QNetworkAccessManager 上传的图像设置 QNetworkRequest::ContentTypeHeader 和 QNetworkRequest::ContentLe