2013-08-27 24 views
5

私はこのコードを持っている:F#の末尾再帰呼び出し

let rec collect (t : BCFile list) (acc : Set<BCFile>) : Set<BCFile> = 
    match t with 
    | [] -> acc 
    | hr::tl -> collect (tl) (Set.union acc (FindSourceFilesForTarget (hr))) 
let s = collect (Set.toList targets) Set.empty 

それは末尾再帰でなければなりませんように見えますが、それは(IL見て)ではありません。尾の再帰を使用するようにコンパイルされない理由は何ですか?

+2

あなたがいますリリースモードでコンパイルしますか?テールコールは、リリースモードでない限り最適化されません。 – mydogisbox

答えて

10

私が知る限り、collect関数は実際にはテール再帰型です。最初のケースでは明らかにaccが返されます。 2番目のケースでは、最初にFindSourceFilesForTargetが呼び出され、次にSet.unionがコールされて戻ります。次のように(より明確に末尾再帰を示している)、それを書き換えることができます:

| hr::tl -> 
    let sources = FindSourceFilesForTarget hr 
    let acc = Set.union acc sources 
    collect tl 

これは自分自身を呼び出すだけの単機能であるため、コンパイラはループにそれを最適化します。 (あなたがC#のにそれを回すために反射板を使用する場合)。これは、どのようにコンパイルされたコードのルックスです:やや無関係なノートで

public static FSharpSet<int> collect(FSharpList<int> t, FSharpSet<int> acc) { 
    while (true) { 
    FSharpList<int> fSharpList = t; 
    if (fSharpList.TailOrNull == null) break; 
    // The following corresponds to the second case 
    FSharpList<int> tl = fSharpList.TailOrNull; 
    int hr = fSharpList.HeadOrDefault; 
    // Variables 'acc' and 't' are mutated (instead of calling the function) 
    acc = SetModule.Union<int>(acc, Program.FindSourceFilesForTarget<int>(hr)); 
    t = tl; 
    } 
    return acc; 
} 

、あなたはまた、標準ライブラリ関数を使用して、これを表現することができます:

t |> Seq.map FindSourceFilesForTarget |> Set.unionMany 
+0

お返事ありがとうございます。 を使用する場合、unionManyを使用すると、パイプラインは使用可能になるとすぐにマージを開始するか、以前のパイプラインステップ(この場合は "Seq.map FindSourceFilesForTarget")からのすべての出力を収集するまで待機しますか? 私が再帰呼び出しをしていた理由は、同じデータがたくさんあり、繰り返しがたくさんあるため(数百の100)、利用可能になったので、組に組合をすることです。したがって、すべての結果をキャッシュしたくないできるだけ早く重複を破棄します。 – phwp

+0

't'が遅延データソース(' IEnumerable')である場合、 'unionMany'オペレーションは要求に応じてそれらを読み込む必要があります(' FindSourceFilesForTarget'も必要に応じて評価されます)。だから私は、この場合、データセット全体が途中でメモリにロードされないと考えています。 –

関連する問題