Выражение находится в хвостовой позиции, если больше ничего не нужно оценивать в текущей функции, следующей за этим выражением. В вашем случае все вызовы recur
в foo
и baz
находятся в хвостовой позиции, и поэтому обе функции будут хорошо компилироваться.
В bar
, однако, ни один из вызовов recur
будет разрешено, потому что ни expr-3
, ни expr-4
не находятся в хвостовой позиции. Есть вызовы функций, которые используют результат каждого вызова recur
в качестве аргумента, то есть вызов функции логически следует за вызовом recur
, и, таким образом, recur
не находится в хвостовой позиции.
Это Нельзя сказать, что вы не можете написать bar
, чтобы сделать рекурсивный вызов сам по себе, но вы должны кодировать его явно, как в:
(defn bar [x]
(if cond-expr-2
(fn-1 (bar expr-3))
(fn-2 (bar expr-4))))
Это абсолютно разрешено, НО эти рекурсивные вызовы из bar
будет использовать пространство стека, что означает, что если функция вызывает себя рекурсивно достаточно раз, вам не хватит места в стеке. recur
(и хвостовая рекурсия в целом) полезны, поскольку не требуют вызова функции в традиционном смысле - вместо этого (логически здесь) аргумент функции в стеке заменяется новым аргументом, и код переходит вернуться к началу функции, поэтому пространство стека не используется. Конечно, это означает, что исходный аргумент при первом вызове функции потерян.
Другие версии Lisp не используют ключевое слово recur
. Когда эти версии Lisp обнаруживают, что функция вызывает себя рекурсивно, они делают то же самое определение "хвостовой позиции", которое делает Clojure, и, если они находят, что вызов находится в хвостовой позиции, они выполняют ту же процедуру "replace-the-arguments-and-jump" "logi c Clojure делает, в то время как если они обнаруживают, что вызов не находится в хвостовой позиции, они испускают код для выполнения" реального "рекурсивного вызова, а не для сбоя компиляции. Преимущество Clojure состоит в том, что для разработчика становится очень очевидным, будет ли вызов скомпилирован как хвост-рекурсивный вызов (ветвь) или нет.