我在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时会出现大型递归错误?
答案 0 :(得分:17)
recur
始终使用尾递归,无论您是重复循环还是函数头。问题是对remove
的调用。 remove
调用first
从底层seq获取元素并检查该元素是否有效。如果通过调用remove
创建了基础seq,则会再次调用first
。如果您在同一个seq上调用remove
20000次,则调用first
需要调用first
20000次,并且所有调用都不能进行尾递归。因此,堆栈溢出错误。
将(remove ...)
更改为(doall (remove ...))
可以解决问题,因为它会阻止remove
次调用的无限堆叠(每个调用都会立即完全应用并返回具体的seq,而不是lazy seq) 。我认为这种方法一次只能将一个候选列表保留在内存中,尽管我对此并不乐观。如果是这样,它的空间效率不会太高,而且一些测试表明它实际上并不慢。