我已经在尾递归和连续传递样式中实现了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]
而不是cc(acc)(rec)
我显然也可以写rec(acc)
。我的结论是否正确,尾递归仅仅是CPS的一个特例,用mapC
写的var rec = acc => {...}
是一个合适的CPS函数?
答案 0 :(得分:1)
为了能够回答这个问题,需要首先澄清这些术语:
如何关联这些条款?
比较尾递归和CPS没有意义,因为这两种技术都代表了控制流应该如何处理的不同范例 - 即使它们有相似之处:
最后一点:描述递归算法的CPS函数将它们的数据存储在匿名函数(闭包)的递归定义环境中。这意味着,CPS不会使用比递归更高效的内存。
答案 1 :(得分:0)
我在纯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通常只包含尾调用,这使得堆栈变得多余 - 而且函数功能如此强大。