Во-первых, вы должны понять, что такое хвостовой вызов.
Tail call - это вызов, который не использует стек.
Теперь вам нужно определить, когда ваш стек потребляет.
Давайте рассмотрим пример фактора:
(defun factorial (n)
(if (= n 1)
1
(* n (factorial (- n 1)))))
Вот нехвостовая рекурсивная реализация факториала.
Зачем? Это связано с тем, что в дополнение к возврату из факториала есть ожидающие вычисления.
(* n ..)
Таким образом, вы складываете n каждый раз, когда вызываете factorial.
Теперь давайте напишем хвостовой рекурсивный факториал:
(defun factorial-opt (n &key (result 1))
(if (= n 1)
result
(factorial-opt (- n 1) :result (* result n))))
Здесь результат передается в качестве аргумента функции.
Таким образом, вы также потребляете стек, но разница в том, что размер стека остается постоянным.
Таким образом, компилятор может оптимизировать его, используя только регистры и оставляя стек пустым.
factorial-opt
тогда быстрее, но менее читабелен.
factorial
ограничен размером стека, factorial-opt
- нет.
Поэтому вы должны научиться распознавать хвостовую рекурсивную функцию, чтобы узнать, ограничена ли рекурсия.
Может существовать методика компиляции для преобразования нехвостной рекурсивной функции в хвостовую рекурсивную. Может быть, кто-то мог бы указать здесь какую-нибудь ссылку.