在clojure中使用recur时溢出

时间:2012-02-08 22:28:14

标签: clojure primes tail-recursion

我在clojure中有一个简单的素数计算器(一种效率低下的算法,但我现在只想了解recur的行为)。代码是:

(defn divisible [x,y] (= 0 (mod x y)))

(defn naive-primes [primes candidates] 
  (if (seq candidates)
      (recur  (conj primes (first candidates)) 
              (remove (fn [x] (divisible x (first candidates))) candidates))
      primes)
)

只要我不想找到太多数字,这就有效。例如

(print (sort (naive-primes [] (range 2 2000))))

的工作原理。对于任何需要更多递归的事情,我都会遇到溢出错误。

    (print (sort (naive-primes [] (range 2 20000))))

不起作用。一般来说,我是否在没有尝试TCO的情况下再次使用复发或再次调用naive-primes似乎没有任何区别。为什么我在使用recur时会出现大型递归错误?

1 个答案:

答案 0 :(得分:17)

recur始终使用尾递归,无论您是重复循环还是函数头。问题是对remove的调用。 remove调用first从底层seq获取元素并检查该元素是否有效。如果通过调用remove创建了基础seq,则会再次调用first。如果您在同一个seq上调用remove 20000次,则调用first需要调用first 20000次,并且所有调用都不能进行尾递归。因此,堆栈溢出错误。

(remove ...)更改为(doall (remove ...))可以解决问题,因为它会阻止remove次调用的无限堆叠(每个调用都会立即完全应用并返回具体的seq,而不是lazy seq) 。我认为这种方法一次只能将一个候选列表保留在内存中,尽管我对此并不乐观。如果是这样,它的空间效率不会太高,而且一些测试表明它实际上并不慢。