F# 使用累加器,仍然出现堆栈溢出异常

发布于 2024-10-01 01:51:37 字数 486 浏览 7 评论 0原文

在下面的函数中,我尝试通过使用累加器来设置尾递归。但是,我遇到了堆栈溢出异常,这让我相信我设置函数的方式没有正确启用尾递归。

//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 中,并且从每一帧返回。显然,我不明白如何正确使用累加器值,因此编译器会进行尾部调用。

In the following function, I've attempted to set up tail recursion via the usage of an accumulator. However, I'm getting stack overflow exceptions which leads me to believe that the way I'm setting up my function is't enabling tail recursion correctly.

//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)

It is my understanding that using the acc would allow the compiler to see that there is no need to keep all the stack frames around for every recursive call, since it can stuff the result of each pass in acc and return from each frame. There is obviously something I don't understand about how to use the accumulator value correctly so the compiler does tail calls.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

我只土不豪 2024-10-08 01:51:37

Stephen Swensen 正确地指出,作为对问题的评论,如果您进行调试,VS 必须禁用尾部调用(否则它不会有堆栈帧跟随调用堆栈)。我知道 VS 这样做了,但只是忘记了。

在了解了这个之后,我想知道运行时或编译器是否有可能抛出一个更好的异常,因为编译器知道您正在调试并且您编写了一个递归函数,在我看来,它可能会给你一个提示,例如

'Stack Overflow Exception: a recursive function does not 
tail call by default when in debug mode'

Stephen Swensen was correct in noting as a comment to the question that if you debug, VS has to disable the tail calls (else it wouldn't have the stack frames to follow the call stack). I knew that VS did this but just plain forgot.

After getting bit by this one, I wonder if it possible for the runtime or compiler to throw a better exception since the compiler knows both that you are debugging and you wrote a recursive function, it seems to me that it might be possible for it to give you a hint such as

'Stack Overflow Exception: a recursive function does not 
tail call by default when in debug mode'
给妤﹃绝世温柔 2024-10-08 01:51:37

使用 .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,我不知道结果会是什么)。您究竟如何使用这个功能? (输入参数。)一旦我更好地了解了该函数的作用,我就可以更新我的答案以希望解决它。

It does appear that this is properly getting converted into a tail call when compiling with .NET Framework 4. Notice that in Reflector it translates your function into a while(true) as you'd expect the tail functionality in F# to do:

[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);
        }
    }
}

Your issue isn't stemming from the lack it being a tail call (if you are using F# 2.0 I don't know what the results will be). How exactly are you using this function? (Input parameters.) Once I get a better idea of what the function does I can update my answer to hopefully solve it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文