.net F#尾递归调用

.net F#尾递归调用,.net,f#,tail-recursion,tail,cil,.net,F#,Tail Recursion,Tail,Cil,我有以下代码: 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 targ

我有以下代码:

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
让rec收集(t:bc文件列表)(acc:Set):设置=
匹配
|[]->acc
|hr::tl->collect(tl)(Set.union acc(FindSourceFilesForTarget(hr)))
设s=collect(Set.toList目标)Set.empty

看起来它应该是尾部递归的,但事实并非如此(看看IL)。你知道为什么不使用尾部递归来编译它吗?

据我所知,
collect
函数实际上是尾部递归的。第一种情况显然只是返回
acc
。第二种情况首先调用
FindSourceFilesForTarget
,然后调用
Set.union
,然后返回。您可以按如下方式重写它(这更清楚地显示了尾部递归):

因为这只是一个调用自身的函数,所以编译器将其优化为一个循环。这就是编译代码的外观(当您使用reflector将其转换为C时):


您是否在版本模式下编译?除非您处于发布模式,否则尾呼叫不会优化。谢谢您的回答。作为一个附带问题,如果使用unionMany,管道会在可用时立即开始合并集合,还是等到从上一个管道步骤收集所有输出(在本例中为“Seq.map FindSourceFilesForTarget”)?我做递归调用的原因是在集合变得可用时对它们进行联合,因为它们有很多相同的数据,并且有很多次迭代(100次或数千次),所以我不想缓存所有结果,而是想在
t
是惰性数据源(
IEnumerable
)时尽快丢弃重复的结果然后,
unionMany
操作应该根据需要读取它们(因此,
FindSourceFilesForTarget
也将根据需要进行评估)。所以我认为在这种情况下,整个数据集不会被加载到内存中。
| hr::tl -> 
    let sources = FindSourceFilesForTarget hr
    let acc = Set.union acc sources
    collect tl
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