5

私はこのコードを持っています:

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 を見ると)。末尾再帰を使用するようにコンパイルされていない理由はありますか?

4

1 に答える 1

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
于 2013-08-27T20:01:14.577 に答える