Można to zrobić w uproszczeniu, zamieniając tę funkcję na CPS (Styl przejścia kontynuacji). Chodzi o to, że zamiast wywoływać depth left
, a następnie obliczać rzeczy na podstawie tego wyniku, wywołuje się depth left (fun dleft -> ...)
, gdzie drugi argument to "co obliczyć, gdy wynik jest dostępny (dleft
)".
let depth tree =
let rec depth tree k = match tree with
| Leaf x -> k 0
| Node(_,left,right) ->
depth left (fun dleft ->
depth right (fun dright ->
k (1 + (max dleft dright))))
in depth tree (fun d -> d)
Jest to dobrze znana sztuczka, która może pełnić dowolną funkcję rekursywną. Voila, to ogon-rec.
Następną znaną sztuczką w torbie jest "defalizacja" wyniku CPS. Reprezentacja kontynuacji (części (fun dleft -> ...)
) jako funkcji jest zadowalająca, ale możesz chcieć zobaczyć, jak to wygląda jako dane. Tak więc zamieniamy każdą z tych zamknięć na konkretny konstruktor typu danych, który przechwytuje wolne zmienne w nim używane.
Tutaj mamy trzy zamknięcia kontynuacji: (fun dleft -> depth right (fun dright -> k ...))
, który ponownie wykorzystuje tylko zmienne środowiskowe right
i k
, (fun dright -> ...)
, który ponownie wykorzystuje k
i teraz-dostępny po lewej wynik dleft
i (fun d -> d)
, początkowy obliczeń, że nic nie uchwycić .
type ('a, 'b) cont =
| Kleft of 'a tree * ('a, 'b) cont (* right and k *)
| Kright of 'b * ('a, 'b) cont (* dleft and k *)
| Kid
Funkcja defunctorized wygląda następująco:
let depth tree =
let rec depth tree k = match tree with
| Leaf x -> eval k 0
| Node(_,left,right) ->
depth left (Kleft(right, k))
and eval k d = match k with
| Kleft(right, k) ->
depth right (Kright(d, k))
| Kright(dleft, k) ->
eval k (1 + max d dleft)
| Kid -> d
in depth tree Kid
;;
Zamiast budować funkcję k
i wykorzystywanie go na liściach (k 0
), zbudować danych typu ('a, int) cont
, która musi być później eval
aby obliczać wynik. eval
, kiedy przechodzi Kleft
, robi to, co robiło zamknięcie (fun dleft -> ...)
, to jest rekurencyjnie wywoływanie depth
w prawym poddrzewie. eval
i depth
są rekursywne.
Teraz spójrz na numer ('a, 'b) cont
, jaki jest ten typ danych? To jest lista!
type ('a, 'b) next_item =
| Kleft of 'a tree
| Kright of 'b
type ('a, 'b) cont = ('a, 'b) next_item list
let depth tree =
let rec depth tree k = match tree with
| Leaf x -> eval k 0
| Node(_,left,right) ->
depth left (Kleft(right) :: k)
and eval k d = match k with
| Kleft(right) :: k ->
depth right (Kright(d) :: k)
| Kright(dleft) :: k ->
eval k (1 + max d dleft)
| [] -> d
in depth tree []
;;
A lista to stos. To, co mamy tutaj, to w rzeczywistości reifikacja (przekształcenie w dane) stosu wywołań poprzedniej funkcji rekursywnej, z dwoma różnymi przypadkami odpowiadającymi dwóm różnym rodzajom połączeń niezwiązanych z końcem.
Należy pamiętać, że defunctionalization jest dostępny tylko dla zabawy. W praktyce wersja CPS jest krótka, łatwa do wyprowadzenia ręcznie, raczej łatwa do odczytania i polecam jej użycie. Zamknięcia muszą być przydzielone w pamięci, ale tak samo są elementy z ('a, 'b) cont
- choć te mogą być reprezentowane w bardziej zwartej formie ". Trzymałbym się wersji CPS, chyba że istnieją bardzo dobre powody, by zrobić coś bardziej skomplikowanego.
wierzę możesz, jeśli przekształcić do kontynuacji przechodzącą stylu. –