2013-08-27 5 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

ARE YOU компиляция в режиме выпуска? Хвост звонков не оптимизирован, если вы не находитесь в режиме деблокирования. – mydogisbox

ответ

10

Насколько я могу судить, функция collect на самом деле является хвостовой рекурсивной. Первый случай явно возвращает acc. Второй случай сначала вызывает 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' также будет оцениваться по запросу). Поэтому я думаю, что в этом случае весь набор данных не будет загружен в память на этом пути. –

Смежные вопросы