尾部位置是表达式将从其返回值的位置。尾部位置的表单被求值后,就没有更多的表单被求值了。
考虑The Joy of Clojure的这个例子
(defn absolute-value [x]
(if (pos? x)
x ; "then" clause
(- x))) ; "else" clause
它接受一个参数并将其命名为 x。如果 x 已经是一个正数,那么 x 是
回来;否则返回 x 的反函数。
if 形式位于函数的尾部位置,因为无论它返回什么,整个
函数将返回。 “then”子句中的 x 也在函数的尾部位置。
但是“else”子句中的 x 不在函数的尾部位置,因为 x 的值
传递给-函数,不直接返回。整个 else 子句 (- x) 在
尾部位置。
在表达式中类似
(if a
b
c)
b 和 c 都位于尾部位置,因为它们中的任何一个都可以从 if 语句中返回。
现在在你的例子中
(loop [x 5
result []]
(if (> x 0)
(recur (dec x) (conj result (+ 2 x)))
result)))
(if ...) 表单位于(loop ...) 表单的尾部,(recur ...) 和result 表单都位于(if ...) 表单的尾部。
另一方面,在您链接的问题中
(fn [coll] (let [tail (rest coll)]
(if (empty tail)
1
(+ 1 (recur tail)))))
recur不是在尾部位置,因为 (+ 1 ...) 将在 (recur tail) 之后进行评估。因此 Clojure 编译器会报错。
尾部位置很重要,因为您可以从尾部位置使用recur 表单。函数式编程语言通常使用递归来实现过程式编程语言通过循环完成的工作。但是递归是有问题的,因为它会消耗堆栈空间,而深度递归会导致堆栈溢出问题(除了速度很慢)。这个问题通常通过tail call optimization (TCO)来解决,当递归调用发生在函数/表单的尾部位置时,它会取消调用者。
因为 Clojure 托管在 JVM 上,并且 JVM 不支持尾调用优化,所以它需要一个技巧来进行递归。 recur 形式就是这个技巧,它允许 Clojure 编译器执行类似于尾调用优化的操作。此外,它还验证recur 确实处于尾部位置。好处是您可以确保优化确实发生了。