3

次の関数では、アキュムレータを使用して末尾再帰を設定しようとしました。ただし、スタックオーバーフローの例外が発生しているため、関数を設定する方法で末尾再帰が正しく有効になっていないと思われます。

//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に詰め込み、各フレームから返すことができるため、コンパイラは、再帰呼び出しごとにすべてのスタックフレームを保持する必要がないことを確認できると理解しています。コンパイラが末尾呼び出しを行うように、アキュムレータ値を正しく使用する方法について私が理解していないことが明らかにあります。

4

2 に答える 2

4

Stephen Swensenは、デバッグする場合、VSは末尾呼び出しを無効にする必要があるという質問へのコメントとして正しいと述べました(そうでない場合、呼び出しスタックを追跡するためのスタックフレームがありません)。私はVSがこれをしたことを知っていましたが、ただ忘れていました。

これに少し触れた後、コンパイラはあなたがデバッグしていることとあなたが再帰関数を書いたことの両方を知っているので、ランタイムまたはコンパイラがより良い例外をスローすることは可能かどうか疑問に思います、それは可能かもしれないと私には思えます次のようなヒントを与える

'Stack Overflow Exception: a recursive function does not 
tail call by default when in debug mode'
于 2010-11-06T02:01:33.480 に答える
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を使用している場合、結果がどうなるかわかりません)。この関数をどの程度正確に使用していますか?(パラメーターを入力します。)関数が何をしているのかがよくわかったら、答えを更新して、うまくいけばそれを解決できます。

于 2010-11-06T01:43:49.757 に答える