Javascript 尾部递归仅仅是CPS的一个特例吗?
我已经用尾部递归和连续传递样式实现了Javascript 尾部递归仅仅是CPS的一个特例吗?,javascript,tail-recursion,continuations,Javascript,Tail Recursion,Continuations,我已经用尾部递归和连续传递样式实现了map。两个版本非常相似: var inc = x => ++x; var xs = [1,2,3,4,5]; var mapR = f => xs => { var rec = acc => { acc[acc.length] = f(xs[acc.length]); return acc.length < xs.length ? rec(acc) : acc; } r
map
。两个版本非常相似:
var inc = x => ++x;
var xs = [1,2,3,4,5];
var mapR = f => xs => {
var rec = acc => {
acc[acc.length] = f(xs[acc.length]);
return acc.length < xs.length ? rec(acc) : acc;
}
return rec([]);
}
mapR(inc)(xs); // [2,3,4,5,6]
var mapC = f => xs => cc => {
var rec = acc => cc => {
acc[acc.length] = f(xs[acc.length]);
return acc.length < xs.length ? cc(acc)(rec) : acc;
}
return cc(rec([])(rec));
}
mapC(inc)(xs)(console.log.bind(console)); // [2,3,4,5,6]
var inc=x=>++x;
var xs=[1,2,3,4,5];
var mapR=f=>xs=>{
var rec=acc=>{
acc[acc.length]=f(xs[acc.length]);
返回acc.lengthxs=>cc=>{
var rec=acc=>cc=>{
acc[acc.length]=f(xs[acc.length]);
返回acc.length
我显然也可以写
rec(acc)
,而不是cc(acc)(rec)
。我的结论正确吗,尾部递归只是CPS的一个特例,用var rec=acc=>{…}
编写的mapC
是一个合适的CPS函数?我将用纯CPS编写如下内容:
const inc = x => cont => cont(x+1);
const map = f => xss => cont => {
if (!xss.length) cont([]);
else f(xss[0])(x => map(f)(xss.slice(1))(xs => cont([x].concat(xs))));
};
// or with an accumulator:
const mapA = f => xs => cont => {
const rec = acc => {
if (acc.length >= xs.length) cont(acc);
else f(xs[acc.length])(x => {
acc.push(x);
rec(acc);
});
}
rec([]);
};
尾部递归仅仅是CPS的一个特例吗
我不会这么说。CPS与递归关系不大。但是,CPS通常只包含尾部调用,这使得堆栈变得多余,而且功能也非常强大。要回答这个问题,需要首先澄清以下术语:
- 直接式和连续传球式是控制流的对立概念
- 尾部递归调用是尾部调用的特殊化
- 递归和尾部递归是直接风格的技术
- 每个(尾部)递归算法都可以转换为其CPS形式,因为CPS比递归具有更大的表达能力
- 当然,两者都可以描述递归控制流
- 两者都没有调用堆栈
- 但是:尾部递归有一个静态控制流,CPS有一个动态控制流(解析下一个调用哪个函数)
最后一句话:描述递归算法的CPS函数将其数据存储在递归定义的匿名函数(闭包)环境中。这意味着,CPS使用内存的效率并不比递归更高。实际上,对于真正的CPS,您不应该
返回。还假设f
需要一个延续。@Bergi但是()=>{a();}
变成()=>{a();返回未定义;}
因此a
将不再处于尾部位置,或者你是什么意思?我的意思是CPS是steriods上的尾部调用,从来没有真正返回任何东西-你没有调用堆栈,你只要跳到下一个继续。当然,这需要一个特殊的解释器,这实际上是一种可怕的编程风格,但是已经构建了POC:-)。只有当您从非cps代码调用cps时,return
s才有意义。是的,肯定只是输入错误:-)谢谢map
使用调用堆栈临时存储每次迭代的x
(相应的inc
结果)。这是否与TCO原则相矛盾?很抱歉,当函数调用处于尾部位置时和不处于尾部位置时,这会让人感到困惑……我要说的是闭包存储了它,x
的生存期绑定到延续的生存期,而不是调用的生存期f
在这里处于尾部调用位置(基本上是每隔一个),堆栈帧可以立即回收。是的,很可怕…