2016-07-01 11 views
7

Ich habe eine Funktion, in der die markierte Zeile eine Konjunktion mit einem rekursiven Aufruf ist. Als Konjunktionen funktionieren, wenn h1 <> h2 dann der rekursive Aufruf nicht gemacht wird. Aber wenn der Aufruf gemacht wird, wird der Compiler dann noch zurückverfolgen und eine ganze Reihe von Konjunktionen über true Werte ausführen? Oder wird es diesen unnötigen Schritt verhindern?Ist die folgende Funktion tail rekursiv?

Mit anderen Worten, ist die folgende Funktion effektiv tail rekursiv?

let isExtensionOf<'A when 'A : equality> (lst1 : list<'A>) (lst2 : list<'A>) : bool = 
    let rec helper (currLst1 : list<'A>) (currLst2 : list<'A>) : bool = 
     match currLst1, currLst2 with 
     | h1 :: _, [] -> false 
     | [], _ -> true 
     | h1 :: t1, h2 :: t2 -> (h1 = h2) && (helper t1 t2) // * 
    helper lst1 lst2 

Ja, ich weiß, dass die Lieblingslinie if h1 = h2 then helper t1 t2 else false geschrieben werden soll. Aber ich bin nur neugierig.

Vielen Dank im Voraus.

+3

Der einfachste Weg, diese Art von Fragen zu beantworten: feed in einer gigantischen Liste, die Sie denken, wird das pathologische Verhalten auslösen und sehen, was passiert. –

+5

Ich würde es lieber kompilieren und dann den resultierenden Code mit ILSpy betrachten. Zuverlässiger. Beachten Sie auch, dass das Ergebnis davon abhängig sein kann, ob Optimierungen aktiviert sind. –

+2

Nun, es hat Listen von Kardinalität '1,00,000,000' (einhundert Millionen) gehandhabt, ohne mit der Wimper zu zucken, also nehme ich einfach an, dass meine Frage bejahend beantwortet werden kann. – Shredderroy

Antwort

7

Ein weiterer einfacher Trick, um herauszufinden, ob die Funktion tailrekursiv ist, besteht darin, eine Ausnahme auszulösen und den Stack-Trace zu betrachten. Zum Beispiel können Sie helper wie folgt ändern:

let rec helper (currLst1 : list<'A>) (currLst2 : list<'A>) : bool = 
    match currLst1, currLst2 with 
    | h1 :: _, [] -> failwith "!" 
    | [], _ -> failwith "!" 
    | h1 :: t1, h2 :: t2 -> (h1 = h2) && (helper t1 t2) 

Wenn Sie jetzt helper [1..10] [1..10] rufen Sie eine Stack-Trace erhalten, die wie folgt aussieht:

System.Exception:
bei FSI_0002.helper [A] (FSharpList'1 currLst1, FSharpList'1 currLst2) in test.fsx. Linie 4
bei $ FSI_0003.main @() aufgrund Gestoppt

auf Fehler Aber Wenn Sie den Code ändern, um nicht-tail-rekursiv zu sein - z durch die letzte Zeile Modifizieren der rekursiven Aufruf ersten (helper t1 t2) && (h1 = h2), dann wird der Stack-Trace zeigt alle rekursiven Aufrufe zu machen:

System.Exception: bei FSI_0004.helper [A] (FSharpList'1 currlst1, FSharpList'1 currlst2) in test.fsx: Zeile 4
bei FSI_0004.helper [A] (FSharpList'1 currlst1, FSharpList'1 currlst2) in test.fsx : Linie 4
bei FSI_0004.helper [A] (FSharpList'1 currLst1, FSharpList'1 currLst2) in test.fsx: Linie 4
bei FSI_0004.helper [A] (FSharpList'1 currLst1, FSharpList'1 currLst2) in test.fsx: Linie 4
bei FSI_0004.helper [A] (FSharpList'1 currLst1, FSharpList'1 currLst2) in test.fsx: Linie 4
bei FSI_0004.helper [A] (FSharpList'1 currLst1, FSharpList '1 currlst2) in test.fsx: Zeile 4
bei FSI_0004.helper [A] (FSharpList'1 currlst1, FSharpList'1 currlst2) in te st.fsx: Linie 4
bei FSI_0004.helper [A] (FSharpList'1 currLst1, FSharpList'1 currLst2) in test.fsx: Linie 4
bei FSI_0004.helper [A] (FSharpList'1 currLst1, FSharpList‘ 1 currLst2) in test.fsx: Linie 4
bei FSI_0004.helper [A] (FSharpList'1 currLst1, FSharpList'1 currLst2) in test.fsx: Linie 4
bei FSI_0004.helper [A] (FSharpList'1 currlst1, FSharpList'1 currlst2) in test.fsx: Zeile 4
um. $ FSI_0005.Haupt @()

+0

Wenn ich etwas nicht vermisse, würde diese Methode nur im Release-Modus funktionieren. Im Debug Modus ist die Tail Call Optimierung deaktiviert (ich meine Tail Calls Tail Checkbox in Eigenschaften generieren). Im Debug-Modus ist dieses Kontrollkästchen standardmäßig deaktiviert. – eternity

6

Von ILSpy scheint es so:

IL_0000: nop 
    IL_0001: newobj instance void class '<StartupCode$ConsoleApplication3>.$Program'/[email protected]<!!A>::.ctor() 
    IL_0006: stloc.0 
    IL_0007: ldloc.0 
    IL_0008: ldarg.1 
    IL_0009: ldarg.2 
    IL_000a: tail. 
    IL_000c: call !!0 class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<!!A>, class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<!!A>>::InvokeFast<bool>(class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!0, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!1, !!0>>, !0, !1) 
    IL_0011: ret