正如我在评论中提到的那样,您可以将程序转换为延续传递风格,然后使用异步函数调用来实现真正的尾调用优化。为了强调这一点,请考虑以下示例:
function foldl(f, a, xs) {
if (xs.length === 0) return a;
else return foldl(f, f(a, xs[0]), xs.slice(1));
}
很明显,这是一个尾递归函数。所以我们需要做的第一件事是将其转换为续传风格,这非常简单:
function foldl(f, a, xs, k) {
if (xs.length === 0) k(a);
else foldl(f, f(a, xs[0]), xs.slice(1), k);
}
就这样。我们的函数现在已经采用了连续传递风格。然而,仍然存在一个大问题——没有尾调用优化。不过,可以通过使用异步函数轻松解决这个问题:
function async(f, args) {
setTimeout(function () {
f.apply(null, args);
}, 0);
}
我们的尾调用优化的
foldl
函数现在可以写成:
function foldl(f, a, xs, k) {
if (xs.length === 0) k(a);
else async(foldl, [f, f(a, xs[0]), xs.slice(1), k]);
}
现在你只需要使用它。例如,如果您想查找数组中数字的总和:
foldl(function (a, b) {
return a + b;
}, 0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function (sum) {
alert(sum);
});
将所有内容组合起来:
function async(f, args) {
setTimeout(function () {
f.apply(null, args);
}, 0);
}
function foldl(f, a, xs, k) {
if (xs.length === 0) k(a);
else async(foldl, [f, f(a, xs[0]), xs.slice(1), k]);
}
foldl(function (a, b) {
return a + b;
}, 0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function (sum) {
alert(sum);
});
当然,在JavaScript中使用继续传递样式是很麻烦的。幸运的是,有一种非常好的语言叫做
LiveScript,它可以让回调变得更加有趣。下面是用LiveScript编写的相同函数:
async = (f, args) ->
setTimeout ->
f.apply null, args
, 0
foldl = (f, a, xs, k) ->
if xs.length == 0 then k a
else async foldl, [f, (f a, xs.0), (xs.slice 1), k]
do
sum <- foldl (+), 0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
alert sum
是的,这是一种编译成JavaScript的新语言,但它值得学习。特别是因为回调函数(即<-
)使您可以轻松地编写不需要嵌套函数的回调。