2016-10-20 16 views
9

mam naiwne realizację gameloopstos wyjątkiem przelewowy przy stosowaniu rur z ogona funkcji rekurencyjnej

let gameLoop gamestate =  
    let rec innerLoop prev gamestate = 
     let now = getTicks() 
     let delta = now - prev 
     gamestate 
     |> readInput delta 
     |> update delta 
     |> render delta 
     |> innerLoop delta    

    innerLoop 0L gamestate 

Realizacja ta generuje stackoverflowexception. Moim zdaniem powinno to być rekurencyjne. Więc moje pytanie jest, dlaczego pierwszy przykład kodu rzuca wyjątek stackoverflow.

+2

Na jakiej platformie działasz? –

+3

Aby wyjaśnić: czy opublikowana wersja robocza działa poprawnie? – Peter

+0

@FyodorSoikin Używam systemu Windows 10 przy użyciu wersji fsi 14.0.23413.0 – Xiol

Odpowiedz

10

Myślę, że odpowiedź jest taka sama jak w wątku Vandroiy links: gdy masz

a 
|> f b 

następnie w trybie debugowania kompilator może skompilować to jak bardzo dosłownej interpretacji

(f b) a 

i jednoznacznie obliczyć f b w jednym kroku i zastosować go do kroku a w drugim etapie. Wywołanie z argumentem a jest nadal wywołaniem ogona, ale jeśli kompilator nie wyemituje prefiksu kodu operacyjnego tail. (ponieważ tailcalls są wyłączone, ponieważ są domyślnie w trybie debugowania), wtedy wyhodujesz stos z wyraźnym zadzwoń i ewentualnie uzyskaj przepełnienie stosu.

Z drugiej strony, jeśli piszesz

f b a 

bezpośrednio potem to nie zdarza: kompilator nie stosuje f częściowo, a zamiast tego będzie wiedział, że jest to bezpośrednie wywołanie rekurencyjne i optymalizować je pod pętla (nawet w trybie debugowania).

+0

Myślę, że pójdę z tym awnser. Jest on najbardziej wzniosły, robi wrażenie na mnie i mogę go odtworzyć w studiu visual. – Xiol

5

myślę to wyjaśnienie, choć zachęcam F ekspertów # kompilatora ważyć się, jeśli jestem poza zasady:

Pierwszy przykład nie jest ogon rekurencyjnej ponieważ wyrażenie w pozycji ogona jest zadzwoń pod numer |>, a nie pod numer innerLoop.

przypominając, że |> jest zdefiniowany jako

let (|>) x f = f x 

gdybyśmy desugar składni rurociągu trochę, gdy dzwonisz

gamestate 
    |> readInput delta 
    |> update delta 
    |> render delta 
    |> innerLoop delta 

jesteś skutecznie numerem:

|> (innerLoop delta) (|> (render delta) (|> (update delta) (|> (readInput delta) gamestate))) 

jako wyraz twojego ciała w funkcji rekursywnej.

Notacja infiksowa nieco zaciemnia to, dzięki czemu wygląda na to, że innerLoop jest w pozycji ogonowej.

+0

Wow, @Vandroiy, to monstrualny wątek z pytaniami i odpowiedziami. I ciężko jest parsować to dla konkluzji, poza tym - jak mówisz - rurociąg może zmylić TCO. – Peter

+0

Tak, ale 'delta innerLoop' jest w pozycji ogonowej' |> ', nie? Tak więc 'innerLoop' powinien tailcall' |> ', który powinien następnie cofnąć' innerLoop' z powrotem lub czy coś mi brakuje? – sepp2k

+0

Ah, przepraszam, usunąłem mój komentarz tuż przed odpowiedzią. Dla jasności usunięty komentarz był linkiem do [tego wątku] (http://stackoverflow.com/questions/35722526/can-does-the-forward-pipe-operator-prevent-tail-call-optimization/35729493), który Włączyłem także komentarze do tego pytania. – Vandroiy

Powiązane problemy