2013-08-27 10 views
5

Ich habe diesen Code:F # tail rekursiven Aufruf

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 

Es sieht aus wie es Schwanz rekursiv sein sollte, aber es ist nicht (bei IL suchen). Irgendeine Idee, warum es nicht kompiliert wird, Tail-Rekursion zu verwenden?

+2

Sind Sie im Freigabemodus kompilieren? Tail-Aufrufe sind nicht optimiert, es sei denn, Sie befinden sich im Freigabemodus. – mydogisbox

Antwort

10

Soweit ich das beurteilen kann, ist die collect Funktion tatsächlich tail-rekursiv. Der erste Fall gibt eindeutig die acc zurück. Der zweite Fall ruft zuerst FindSourceFilesForTarget auf, ruft dann Set.union auf und kehrt dann zurück. Man könnte es wie folgt umschreiben (das den Schwanz Rekursion deutlicher zeigt):

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

Da dies nur eine einzige Funktion selbst Aufruf ist, der Compiler es in eine Schleife optimiert. Dies ist, wie der kompilierte Code aussieht (wenn Sie Reflektor verwenden, um C# drehen):

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; 
} 

Auf einer leicht in keinem Zusammenhang zur Kenntnis, man könnte auch dies mit Standard-Bibliothek Funktionen ausdrücken:

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

danke für die Antwort. als eine Nebenfrage, wenn Sie unionMany verwenden, würde Pipeline beginnen Zusammenführen von Mengen, sobald es verfügbar wird oder wartet, bis es alle Ausgaben aus vorherigen Pipeline-Schritt ("Seq.map FindSourceFilesForTarget" in diesem Fall) sammelt? der Grund, warum ich rekursive Aufrufe getan habe, ist Union auf Sets zu tun, wie sie verfügbar werden, da sie viele gleiche Daten und viele Iterationen (100 von Tausenden) haben, so wollte ich nicht alle Ergebnisse zwischenspeichern und wollte Verwerfen von Duplikaten so schnell wie möglich – phwp

+0

Wenn 't' eine Lazy-Datenquelle ist (' IEnumerable'), sollte die 'unionMany'-Operation sie bei Bedarf lesen (und so wird 'FindSourceFilesForTarget' auf Anfrage ebenfalls ausgewertet). Also denke ich, dass in diesem Fall der gesamte Datensatz nicht in den Speicher geladen wird. –