在以下函数中,我试图通过使用累加器来设置尾递归。但是,我得到了堆栈溢出异常,这使我相信我设置函数的方式是不能正确启用尾递归。
//F# attempting to make a tail recursive call via accumulator
let rec calc acc startNum =
match startNum with
| d when d = 1 -> List.rev (d::acc)
| e when e%2 = 0 -> calc (e::acc) (e/2)
| _ -> calc (startNum::acc) (startNum * 3 + 1)
我的理解是,使用acc
将允许编译器看到没有必要为每个递归调用保留所有堆栈帧,因为它可以填充每个传递的结果在acc和从每一帧返回。很明显我不明白如何正确使用累加器值,因此编译器会执行尾调用。
答案 0 :(得分:3)
斯蒂芬·斯文森(Stephen Swensen)正确地指出这样一个问题:如果你调试,VS必须禁用尾调用(否则它不会让堆栈帧跟随调用堆栈)。我知道 VS做了这件事,但只是忘了。
在得到这个之后,我想知道运行时或编译器是否可能抛出一个更好的异常,因为编译器知道你正在调试并且你写了一个递归函数,在我看来它可能是可能的它给你一个提示,如
'Stack Overflow Exception: a recursive function does not
tail call by default when in debug mode'
答案 1 :(得分:1)
看起来在使用.NET Framework 4进行编译时正确地转换为尾调用。请注意,在Reflector中,它会将您的函数转换为while(true)
。期望F#中的尾部功能可以:
[CompilationArgumentCounts(new int[] { 1, 1 })]
public static FSharpList<int> calc(FSharpList<int> acc, int startNum)
{
while (true)
{
int num = startNum;
switch (num)
{
case 1:
{
int d = num;
return ListModule.Reverse<int>(FSharpList<int>.Cons(d, acc));
}
}
int e = num;
if ((e % 2) == 0)
{
int e = num;
startNum = e / 2;
acc = FSharpList<int>.Cons(e, acc);
}
else
{
startNum = (startNum * 3) + 1;
acc = FSharpList<int>.Cons(startNum, acc);
}
}
}
你的问题不是因为缺少它是一个尾调用(如果你使用F#2.0我不知道结果会是什么)。你究竟是如何使用这个功能的? (输入参数。)一旦我对函数的作用有了更好的了解,我就可以更新我的答案,希望能够解决它。