为什么这会溢出堆栈而不是使用尾递归?

时间:2013-09-07 21:50:46

标签: clojure

我在Clojure中定义了以下函数。

; return the input unchanged
(defn same [x] x)

; Recursively call the function on the input N times
(defn recurse-n-times [input function n]
  (if (= n 0)
    input
    (recurse-n-times (function input) function (- n 1))
  )
)

以下是我的递归函数的一些输出:

(recurse-n-times 0 inc 5)     ; returns 5, as expected
(recurse-n-times 0 same 5)    ; returns 0, as expected
(recurse-n-times 0 same 5000) ; StackOverflowError:
                              ; clojure.lang.Numbers$LongOps.combine
                              ; (Numbers.java:394)

我不明白为什么我得到StackOverflowErrorrecurse-n-times做的最后一件事就是调用自身,所以我希望它能使用尾递归而不会增长堆栈。

希望期望这个替代定义能够提供StackOverflowError

(defn bad-recurse-n-times [input function n]
  (if (= n 0)
    input
    (function (alt-recurse-n-times input function (- n 1)))
  )
)

为什么recurse-n-times不使用尾递归?

2 个答案:

答案 0 :(得分:14)

这是JVM的限制,而不是Clojure的限制。 JVM不支持TCO。

Clojure为此提供了一个特殊表格,recur

(defn recurse-n-times [input function n]
  (if (= n 0)
  input
  (recur (function input) function (- n 1))))

(recurse-n-times 0 same 500000) ;; will work

重复形式应出现在尾部位置,否则Clojure编译器会抱怨它。

  

请注意,recur是Clojure中唯一不占用堆栈的循环结构。没有尾调用优化,并且不鼓励使用自调用来循环未知边界。 recur是功能性的,它在尾部位置的使用由编译器验证。

答案 1 :(得分:4)

根据clojure.org

  

没有尾调用优化,请使用recur。

所以你必须使用“复发”特殊形式才能做到这一点。