Когда это нужно
Хвостовые вызовы важны, когда цикл естественно записывается рекурсией. Если рекурсивный вызов — последнее действие, Лиспекс заменяет текущее продолжение вместо накопления нового.
Проверьте запуском
(let loop ((n 10000) (acc 0)) (if (= n 0) acc (loop (- n 1) (+ acc 1))))Наблюдаемый результат
10000Разбор примера
Именованный let создаёт loop. Каждая ветвь либо возвращает acc, либо сразу вызывает loop с меньшим n и новым acc; после вызова не остаётся сложения. Поэтому десять тысяч шагов работают как один логический цикл.
Как рассуждать
- Хвостовые позиции включают последние тела, обе ветви
if, нормализованныеcond/case, последниеand/or,do,call-with-valuesиapply. - Правильный хвостовой вызов относится к росту продолжения, а не к одинаковому времени или неограниченному выполнению.
- Даже для хвостовой рекурсии действуют детерминированные профильные пределы ресурсов.
Быстрый выбор
| Форма | Хвостовой вызов? | Причина |
|---|---|---|
(if done? answer (loop next)) | да | результат выбранной ветви возвращается прямо |
(begin (display x) (loop next)) | да | вызов — последняя форма |
(+ x (loop next)) | нет | после рекурсии остаётся сложение |
(map loop items) | нет для функции обратного вызова | map ещё обрабатывает результат |
(apply loop args) в хвостовой позиции | да | apply сохраняет хвостовую позицию |
Частая ошибка
Нехвостовая рекурсия может достичь объявленного предела ресурсов.
Текущие границы
- Чистая ресурсная ошибка не доказывает равенство порогов Native и WASM.
Куда дальше
Руководство по рекурсии показывает базовый случай и аккумулятор, а глава о ресурсах объясняет, почему хвостовая безопасность не означает безграничность.