我花点时间完整讲一下吧。
如前面所说, fact (n, ret) 函数中的
// fact (n-1, function(t0) {
// ret(n*t0)
// })
就是在构建一个新的 continuation 的过程, 并把原来的再包进去。只要分步把这个函数的 continuation 一步一步展开,那就明白了。
举个🌰,展开 fact (3, ret) ,
首先 fact (3, ret) 展开成 fact (2, function(t0){ ret(3 * t0) })
知道作者为什么设一个 t0 吗?因为还有 t1, t2, t3 ,天生骄傲。(本人 t1 锤粉转黑)
然后 fact (2, ...) 展开成 fact (1, function(t1){ (function(t0){ret(3*t0})(2*t1) })
再然后 fact (1, ...) => fact(0, function(t2){ ( function(t1){ (function(t0){ret(3*t0})(2*t1) })(1*t2) })
最后 fact(0, ...) => (function(t2){(function(t1){ (function(t0){ret(3*t0})(2*t1) })(1*t2) })(1)
代入所有参数得到 ret(3*2*1*1) 就是结果(马德绕了那么一大圈)
其实段代码看似 “尾递归优化” ,但是其实跟尾递归优化一点关系都没有,而且前面所说的能优化函数栈也是扯蛋,这只是把 fact 函数调用时候的函数栈转嫁到了 continaution 上了罢了,一点都没有优化。不信我们把这个所谓 “尾递归优化” 的代码转成循环给你们看看结果。转换成循环的方式在我博客里面有
http://lovearia.me/article/show/9 // function fact(arg_n, arg_ret) {
// let n = arg_n;
// let ret = arg_ret;
//
// while (true){
// if (n == 0){
// ret(1);
// break;
// }
// else{
// let _n = n;
// let _ret = ret;
// n = _n - 1;
// ret = function(t0){_ret(_n*t0)}; // <= 会在这里爆栈,根本没有任何优化效果
// continue;
// }
// }
// }
至于前面有人提到 cps 执行的过程中可以优化函数栈,但是这时候为了递归优化而写成 cps 形式其实是很没有意义的,因为它实质就是尾递归优化,并且还需要很庞大的空间消耗来构建这个 continuation ,这和直接递归几乎没差别。这个 cps 递归只有在类似 haskell 这类惰性求值的语言中才能很好的优化,但是问题是在 haskell 这类惰性求值的语言中根本这种利用 cps 递归的写法本身又没有意义…