Po pierwsze, powinieneś zrozumieć, o co chodzi w ogonie.
Ogonowe połączenie to połączenie, które nie zużywa stosu. Teraz musisz rozpoznać, kiedy zużywasz stos.
Weźmy silnia przykład:
(defun factorial (n)
(if (= n 1)
1
(* n (factorial (- n 1)))))
Oto non-tail rekurencyjna realizacja silnia. Dlaczego? Wynika to z faktu, że oprócz zwrotu z silni istnieje oczekujące obliczenie.
(* n ..)
A więc układasz n za każdym razem, gdy wywołasz silnię. Teraz napisać ogon rekurencyjnej silnia:
(defun factorial-opt (n &key (result 1))
(if (= n 1)
result
(factorial-opt (- n 1) :result (* result n))))
Tu wynik jest przekazywany jako argument do funkcji. Więc zużywasz również stos, ale różnica polega na tym, że rozmiar stosu pozostaje stały. W ten sposób kompilator może zoptymalizować go, używając tylko rejestrów i pozostawiając pusty stos.
Urządzenie factorial-opt
jest szybsze, ale jest mniej czytelne. factorial
jest ograniczona do wielkości stosu, nie będzie to factorial-opt
. Powinieneś nauczyć się rozpoznawać funkcję rekurencyjną ogona, aby wiedzieć, czy rekurencja jest ograniczona.
Może istnieć technika kompilacji, która przekształca nierekultywną funkcję rekursywną w rekursywny ogon. Może ktoś mógłby wskazać jakiś link tutaj.
Ciesząc się rekurencją, możesz cieszyć się młotkiem. Czasami jest to najlepsze dostępne narzędzie, ale gdy chcesz wkręcić śrubę, sięgnij po śrubokręt. – Xach
Podoba mi się pomysł rekursji. Fascynuje mnie. Jednak nigdy nie używałbym tego w niczym komercyjnym. Oczywiście ułatwia to pisanie kodu, ale odbywa się kosztem wydajności i zużycia pamięci. Preferuję wersje iteracyjne, aby być bezpiecznym. –