2013-08-27 11 views
5

mam ten kod:F # ogon wywołanie rekurencyjne

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 

Wygląda na to powinno być rekurencyjne ogon, ale to nie jest (patrząc na IL). Każdy pomysł, dlaczego nie jest skompilowany do korzystania z rekursji ogon?

+2

Czy jesteś kompilacja w trybie wydania? Ogonowe połączenia nie są optymalizowane, chyba że jesteś w trybie zwolnienia. – mydogisbox

Odpowiedz

10

O ile mogę stwierdzić, funkcja collect jest rzeczywiście rekursywna. Pierwszy przypadek po prostu zwraca wartość acc. Drugi przypadek najpierw wywołuje FindSourceFilesForTarget, a następnie wywołuje Set.union, a następnie zwraca. Można go przepisać następująco (co pokazuje ogon-rekurencji jaśniej):

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

Ponieważ jest tylko jedna funkcja nazywająca siebie, kompilator optymalizuje go w pętli. W ten sposób skompilowany kod wygląda (przy użyciu reflektor, aby włączyć go do 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; 
} 

na nieco niepowiązanych nuty, można również wyrazić to za pomocą standardowych funkcji biblioteki:

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

dziękuję za odpowiedź. jako pytanie poboczne, jeśli użyje się unionMany, czy rurociąg rozpocznie scalanie zbiorów, gdy tylko stanie się dostępny lub poczeka, aż zbierze wszystkie wyniki z poprzedniego kroku potoku ("Seq.map FindSourceFilesForTarget" w tym przypadku)? Powodem, dla którego robiłem wywołania rekursywne, jest tworzenie związków na zestawach, ponieważ stają się one dostępne, ponieważ mają wiele takich samych danych i istnieje wiele iteracji (setki tysięcy), więc nie chciałem buforować wszystkich wyników i chciałem odrzuć duplikaty tak szybko, jak to możliwe – phwp

+0

Gdy 't' jest leniwym źródłem danych (' IEnumerable'), wówczas operacja 'unionMany' powinna je odczytać na żądanie (a więc' FindSourceFilesForTarget' będzie również oceniany na żądanie). Myślę więc, że w tym przypadku cały zestaw danych nie zostanie załadowany do pamięci po drodze. –