Clojure:复发的确切位置是什么?
clojure中复发的“尾部位置”的精确定义是什么。我认为它将是循环S表达式中的最后一项,但在下面的示例中,以(if…)开头的S表达式似乎位于尾部位置,即([循环关键字][绑定语句][if语句]) 代码取自Clojure:复发的确切位置是什么?,clojure,Clojure,clojure中复发的“尾部位置”的精确定义是什么。我认为它将是循环S表达式中的最后一项,但在下面的示例中,以(if…)开头的S表达式似乎位于尾部位置,即([循环关键字][绑定语句][if语句]) 代码取自 密切相关的问题:尾部位置是表达式返回值的位置。在评估尾部位置的表单后,将不再评估其他表单。 考虑一下这个例子 它接受单个参数并将其命名为x。如果x已经是正数,那么x是 返回;否则,返回x的对立面。 if表单位于函数的尾部位置,因为无论它返回什么,整个 函数将返回。“then”子句中的x也位于
密切相关的问题:尾部位置是表达式返回值的位置。在评估尾部位置的表单后,将不再评估其他表单。 考虑一下这个例子 它接受单个参数并将其命名为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…
表单的尾部位置,(recurr…
表单和结果
表单都位于(if…
表单的尾部位置
另一方面,在你链接的问题中
(fn [coll] (let [tail (rest coll)]
(if (empty tail)
1
(+ 1 (recur tail)))))
重复
未处于尾部位置,因为(+1…
将在(重复尾部)
之后计算。因此Clojure编译器会给出一个错误
尾部位置很重要,因为您可以从尾部位置使用recur
表单。函数式编程语言通常使用递归实现过程式编程语言通过循环实现的功能。但是递归是有问题的,因为它消耗堆栈空间,深度递归可能导致堆栈溢出问题(除了速度慢之外)。这个问题通常通过尾部调用优化(tail call optimization,TCO)来解决,当递归调用发生在函数/表单的尾部位置时,它将取消调用者
因为Clojure托管在JVM上,并且JVM不支持尾部调用优化,所以它需要一个实现递归的技巧。
recur
表单就是这个技巧,它允许Clojure编译器执行类似于尾部调用优化的操作。此外,它还验证了recur
确实处于尾部位置。好处是,您可以确保优化确实发生。为了补充上面Paul的优秀答案,《Clojure的喜悦》(ed1)提供了一个表格(表7.1),以各种形式/表达式精确显示尾部位置,我在下面复制了该表格。查找每个表单/表达式中“tail”一词出现的位置:
|---------------------+-------------------------------------------+---------------|
| Form | Tail Position | recur target? |
|---------------------+-------------------------------------------+---------------|
| fn, defn | (fn [args] expressions tail) | Yes |
| loop | (loop [bindings] expressions tail) | Yes |
| let, letfn, binding | (let [bindings] expressions tail) | No |
| do | (do expressions tail) | No |
| if, if-not | (if test then tail else tail) | No |
| when, when-not | (when test expressions tail) | No |
| cond | (cond test test tail ... :else else tail) | No |
| or, and | (or test test ... tail) | No |
| case | (case const const tail ... default tail) | No |
|---------------------+-------------------------------------------+---------------|
|---------------------+-------------------------------------------+---------------|
|形成|尾部位置|重复目标|
|---------------------+-------------------------------------------+---------------|
|fn,defn |(fn[args]表达式尾部)|是的|
|循环|(循环[绑定]表达式尾部)|是|
|let,letfn,binding |(让[bindings]表达式结尾)|不|
|不要(不要尾巴)|不要|
|如果,如果不是|(如果是测试,则为其他尾部)|否|
|何时,何时不|(当测试表达式尾部时)|否|
|cond |(cond test test tail…:else tail)|否|
|或|(或测试…尾巴)|否|
|案例|(案例常量尾部…默认尾部)|否|
|---------------------+-------------------------------------------+---------------|
谢谢,很好的解释,我的困惑是我认为尾部位置是S表达式中的一个位置,而不是最终的评估。根据您的解释,似乎可以在循环中使用多个递归形式(例如在final IF s-exp的两个分支中),对吗?是的,这是完全可能的。我记得我也被这件事弄糊涂了。此外,我一直在读关于由于JVM上缺乏TCO,recur是如何如此重要的。。。我一点都不明白(例如,TCO代表什么…),我的脑袋在打转……是的,这很令人困惑,我从来没有看到任何文档解释过。也许我会建议他们根据您的答案在recur文档中添加一些内容。@atc我现在写它感觉很好;)谢谢和clojure玩得开心!再次感谢您帮助新手理解尾部递归。:-)
(fn [coll] (let [tail (rest coll)]
(if (empty tail)
1
(+ 1 (recur tail)))))
|---------------------+-------------------------------------------+---------------|
| Form | Tail Position | recur target? |
|---------------------+-------------------------------------------+---------------|
| fn, defn | (fn [args] expressions tail) | Yes |
| loop | (loop [bindings] expressions tail) | Yes |
| let, letfn, binding | (let [bindings] expressions tail) | No |
| do | (do expressions tail) | No |
| if, if-not | (if test then tail else tail) | No |
| when, when-not | (when test expressions tail) | No |
| cond | (cond test test tail ... :else else tail) | No |
| or, and | (or test test ... tail) | No |
| case | (case const const tail ... default tail) | No |
|---------------------+-------------------------------------------+---------------|