Recursion Clojure-使用递归与普通递归函数调用

Recursion Clojure-使用递归与普通递归函数调用,recursion,clojure,Recursion,Clojure,为什么Clojure有“重现”的特殊形式 当我用函数本身替换“recur”时,我得到了相同的结果: (defn print-down-from [x] (when (pos? x) (println x) (recur (dec x)) ) ) (print-down-from 5) 结果与 (defn print-down-from [x] (when (pos? x) (println x) (print-down-from (dec x

为什么Clojure有“重现”的特殊形式

当我用函数本身替换“recur”时,我得到了相同的结果:

(defn print-down-from [x]
  (when (pos? x)
    (println x)
    (recur (dec x))
    )
  )
(print-down-from 5)
结果与

(defn print-down-from [x]
  (when (pos? x)
    (println x)
    (print-down-from (dec x))
    )
  )
(print-down-from 5)
我想知道“recur”是否只是一种安全措施,这样,如果开发人员碰巧使用非尾部递归,编译器就会抛出错误。或者编译器有必要优化尾部递归


但我最想知道的是除了堆栈消耗以外的其他原因。

据我所知,
循环。。recur
使用尾部递归,因此程序不会破坏堆栈,而常规递归也不会。不使用
loop.解决收尾问题的一些方法。。recur
,因为——我在做一个有根据的猜测——只能使用直接的递归函数调用而不是
循环来推导解决方案。。重复出现

据我所知,在几年前的一些Clojure书籍中,您应该可以随意使用
loop。。重复出现

然而,至少从我读过的那些书中的讨论以及从我收到的关于我在SO中提出的Clojure问题的答案来看,人们普遍认为应该首先使用
map
之类的结构来解决你的问题。如果这是不可能的,那么尽一切可能使用
loop。。重复执行
,如果您不认为有可能破坏堆栈,则执行直接递归调用。

如中所述:

在缺少可变局部变量的情况下,循环和迭代必须采用与内置for或while结构的语言不同的形式,这些结构由更改状态控制。在函数式语言中,循环和迭代通过递归函数调用被替换/实现许多这样的语言保证在尾部位置进行的函数调用不会占用堆栈空间,因此递归循环利用常量空间。由于Clojure使用Java调用约定,因此它不能也不会做出相同的尾部调用优化保证。相反,它提供了recur特殊运算符,,它通过重新绑定并跳到最近的封闭循环或函数帧来执行常量空间递归循环。虽然不像尾部调用优化那样通用,但它允许大多数相同的优雅构造,并提供检查重复调用只能发生在尾部位置的优势

当您不使用
recur
(或
trampoline
)时,您的函数调用将消耗堆栈:因此,如果您运行
(从100000向下打印)
,您将很快观察到崩溃


因此,提供这种语言设施有几个好处:

  • 与传统的递归调用(如问题示例所示)相反,使用
    recur
    不会消耗堆栈
  • 作者知道正在使用TCO(并且堆栈没有被消耗),因为在不可能实现TCO的位置使用会导致编译时失败。因此,堆栈消耗特性对于代码的作者和读者来说都是显而易见的,这与只有自动TCO的语言不同(在这种语言中,在知道调用是否优化之前,需要仔细阅读——考虑宏扩展——以确定调用是否真的处于尾部位置)
  • 与传统JVM调用约定的兼容性,以及与以其他JVM为中心的语言编写的代码的本机互操作性得到了保留

最后,有关背景信息,请参阅有关StackOverflow主题的其他几个问题之一:

  • (询问是否有一个自动工具来明确调用
    recur
    不必要)
  • (询问上述引用的clojure.org文档中提出的自动TCO不能在没有JVM支持或违反JVM调用约定的情况下实现,从而损害互操作性的说法背后的依据)
  • (同样,更一般地说)

Clojure没有实现自动TCO,所以您的另一种方法给出了“相同的结果”,实际上是在消耗堆栈,因此,如果使用的数量足够大,它将失败……自动TCO与Clojure函数遵循与常规Java函数相同的调用约定,从而具有Clojure提供的完全透明的互操作性是不兼容的。另请参见相关:以及进一步:“除了最深、最困难的问题外,你应该解决所有问题,[没有]
循环。。重复出现
“--需要引证。@CharlesDuffy我没有引证。这是我在各种Clojure书籍中看到的,以及对我在这里提出的问题的评论/回答。作为在Clojure商店编写代码的人,我称之为恶作剧。这不是一种很好的代码味道——如果有一种解决方案可以使用
reduce
,那么它可能更为惯用[尽管可能性能也较差]——但它决不是最好直接避免的工具,而且在函数调用开销不平凡且可测量的现实世界中,这通常是正确的。@CharlesDuffy我不是建议使用
loop..recurr
无论如何都要避免。我建议我在2011年读到的一些最早的书中首先尝试使用
map
之类的结构来解决您的问题。然后,如果需要,使用
loop。。重现
“如果有一个解决方案使用了
reduce
,那么这个解决方案可能更惯用[尽管可能性能较差]”——需要引用有关性能的内容。