continuation 教程: 理解 CPS
这是一个 continuation 系列教程:
我们来由浅入深地系统学习下 continuation 的原理以及应用场景。这个系列教程的内容和王垠的 continuation 专项班无关,是我自己学习和研究的成果,所以不会有版权问题。不过当然正是因为我学习了基础班,打下了坚实的基础,才知道该如何去自学和理解 continuation 这个概念。这篇文章会少量透露出基础班学到的技能,毕竟 continuation 属于基础班的进阶内容,无法跳过基础技能去理解。
递归
首先用递归的形式写一个阶乘函数 fact,我们已经很熟悉它的写法,不需要过多解释:
function fact(n){ if (n === 0) { return 1; } else { return n * fact(n - 1); }}console.log("fact1=", fact(1)); // 1console.log("fact3=", fact(3)); // 6console.log("fact5=", fact(5)); // 120尾递归
接着把 fact 函数改为尾递归的形式。尾递归会比递归多一个参数,新参数用来保存每个调用计算后的值:
function factTail(n, prod){ if (n == 0) { return prod; } else { return factTail(n-1, prod*n); }}console.log("factTail1=", factTail(1, 1)); // 1console.log("factTail3=", factTail(3, 1)); // 6console.log("factTail5=", factTail(5, 1)); // 120CPS 形式
我们基于 fact 函数的尾递归形式,再新增一个参数 k,这个 k 是一个函数,fact 不直接返回计算后的值,而是结果值对 k 函数的调用,像这样:
function factTailCPS(n, prod, k){ if (n == 0) { return k(prod); } else { return factTailCPS(n-1, prod*n, k); }}factTailCPS( 1, 1, x => console.log("factTailCPS1=", x) ); // 1factTailCPS( 3, 1, x => console.log("factTailCPS1=", x) ); // 6factTailCPS( 5, 1, x => console.log("factTailCPS1=", x) ); // 120这个 k 就是 continuation,意味着告诉 fact 函数,你执行完了计算出结果之后,应该如何进行下一步延续。不用怀疑,这个函数完全符合 CPS(Continuation-Passing-Style)的形式。
典型 CPS
但是用尾递归结合 continuation 参数的形式,显然不够简洁,并不算典型的 CPS 形式。典型的 CPS 形式比较难理解,所以不需要自己思考出来,直接看这个现成的例子,我们对递归形式的 fact 函数改进一下:
function factCPS(n, k){ if (n == 0) { return k(1); } else { return factCPS(n-1, r => k(n * r)); }}可能看着有点懵,不要慌,我们拆解一下其中的内容。首先 k 仍然代表 continuation,并且...
剩余内容已隐藏