Próbowałem zrozumieć kontynuacje/CPS i na podstawie tego, co mogę zebrać, buduję opóźnione obliczenia, kiedy dojdziemy do końca listy, wywołujemy ostateczne obliczenia.dlaczego kontynuacje unikają stackoverflow?
Czego nie rozumiem, to dlaczego CPS zapobiega stackoverflow, gdy wydaje się analogiczne do budowania funkcji zagnieżdżonej, jak na naiwne podejście w przykładzie 1. Przepraszamy za długi post, ale próbował pokazać pomysł (i ewentualnie gdzie to idzie źle) od podstaw:
Więc:
let list1 = [1;2;3]
Przykład 1: "naiwne podejście"
let rec sumList = function
|[] -> 0
|h::t -> h + sumList t
Więc kiedy to skończy, iteracyjnie skutkuje:
1 + sumList [2;3]
1 + (2 + sumList [3])
1 + (2 + (3 + 0))
Więc zagnieżdżenie (i kwestie przelewowe) mogą być przezwyciężone przez Tail rekursji - działa akumulator tj.
"Przykład 2: Rekursja ogona"
let sumListACC lst =
let rec loop l acc =
match l with
|[] -> acc
|h::t -> loop t (h + acc)
loop lst 0
tj
sumList[2;3] (1+0)
sumList[3] (2+1)
sumList[] (3+3)
tak dlatego, że akumulator jest oceniana na każdym kroku, nie ma gniazdowania i unikamy pęknięcie stos . Jasny!
Dalej przychodzi CPS, Rozumiem, że jest to wymagane, gdy mamy już akumulator, ale funkcja nie jest rekurencyjna, np. z foldback. Chociaż nie jest to wymagane w powyższym przykładzie, stosując CPS tego problemu daje:
"Przykład 3: CPS"
let sumListCPS lst =
let rec loop l cont =
match l with
|[] -> cont 0
|h::t -> loop t (fun x -> cont(h + x))
loop lst (fun x -> x)
mojego rozeznania, iteracyjnie to może być zapisany jako:
loop[2;3] (fun x -> cont (1+x))
loop[3] (fun x ->cont (1+x) -> cont(2+x))
loop[] (fun x -> cont (1+x) -> cont(2+x) -> cont (3+x)
, która następnie zmniejsza się sekwencyjnie od prawej z ostatecznym x = 0
i.e:
cont(1+x)-> cont(2+x) -> cont (3+0)
cont(1+x)-> cont(2+x) -> 3
cont(1+x) -> cont (2+3)
- ...
cont (1+5) -> 6
który Przypuszczam jest analogiczna do:
cont(1+cont(2+cont(3+0)))
(1+(2+(3+0)))
korekta do oryginalnej wiadomości - zrozumiał, że jest oceniany z prawej strony, na przykład zastępując cont(h +x)
z cont(h+2*x)
daje 17
dla powyższego przykładu zgodny z: (1+2*(2+2*(3+2*0)))
czyli dokładnie tam, gdzie zaczęliśmy w przykładzie 1, na podstawie tego skoro wciąż musimy śledzić, skąd pochodzimy, dlaczego użycie tego zapobiega problemowi przepełnienia, którego przykład 1 cierpi?
Jak wiem, nie mam, gdzie się pomyliłem?
Przeczytałem następujące posty (wiele razy), ale powyższy błąd pozostaje.
http://www.markhneedham.com/blog/2009/06/22/f-continuation-passing-style/
http://codebetter.com/matthewpodwysocki/2008/08/13/recursing-on-recursion-continuation-passing/
http://lorgonblog.wordpress.com/2008/04/05/catamorphisms-part-one/
Inną kwestią jest, że możliwe do odzyskania obiekty (funkcje lambda) na stercie są odzyskiwane podczas GC. Podczas korzystania ze stosu klatki stosu gromadzą się, nawet jeśli nigdy nie będą potrzebne. – t0yv0
Bardzo prawdziwe. W sumie - sterty są lepszą lokalizacją dla wszelkich niebanalnych rzeczy. Właśnie dlatego sterty zostały stworzone. –