Recursion 如何避免clojure递归函数中的堆栈溢出?

Recursion 如何避免clojure递归函数中的堆栈溢出?,recursion,clojure,stack-overflow,Recursion,Clojure,Stack Overflow,以下是一个例子: ;; Helper function for marking multiples of a number as 0 (def mark (fn [[x & xs] k m] (if (= k m) (cons 0 (mark xs 1 m)) (cons x (mark xs (inc k) m)) )))

以下是一个例子:

;; Helper function for marking multiples of a number as 0
(def mark (fn [[x & xs] k m]
                 (if (= k m) 
                   (cons 0 (mark xs 1       m))
                   (cons x (mark xs (inc k) m))
                   )))

;; Sieve of Eratosthenes
(defn sieve
  [x & xs]
  (if (= x 0)
    (sieve xs)
    (cons x (sieve (mark xs 1 x)))
    ))

(take 10 (lazy-seq (sieve (iterate inc 2))))
它会产生堆栈溢出错误

需要终止条件吗 这里的问题是
标记
筛选
函数都没有终止条件。必须有一组输入,每个函数不为其自身调用,而是返回一个答案。此外,这些函数的每一组(有效)输入最终应解析为非递归返回值

但即使你做对了。。。 我要补充的是,即使您成功地创建了正确的终止条件,如果递归的深度太大,仍然有可能出现堆栈溢出。这可以通过增加JVM堆栈大小在一定程度上缓解,但这有其局限性


对于某些函数来说,解决这个问题的方法是使用尾部调用优化。一些递归函数是尾部递归的,这意味着对在其定义中定义的函数的所有递归调用都位于尾部调用位置(是在定义体中调用的最终函数)。例如,在
sieve
函数的
(=x 0)
情况下,
sieve
是尾部调用,因为
sieve
的结果不会传递到任何其他函数中。但是,在
(而不是(=x 0))
的情况下,调用
sieve
的结果会传递给
cons
,因此这不是尾部调用。当函数是完全尾部递归函数时,可以在后台将函数定义转换为循环构造,从而避免消耗堆栈。在clojure中,这可以通过在函数定义中使用
recur
而不是函数名来实现(还有一个
循环
构造,有时可能会有所帮助)。同样,因为不是所有递归函数都是尾部递归的,所以这不是万能的。但是,当他们感到满意时,最好知道你可以做到这一点。

这里有几个问题。首先,正如在另一个答案中指出的,您的
标记
筛选
函数没有终止条件。看起来它们是设计用来处理无限序列的,但是如果你传递了一个有限长度的序列,它们会一直离开末端

这里更深层次的问题是,您似乎试图让一个函数通过递归调用自身来创建一个惰性无限序列。然而,
cons
在任何方面都不是懒惰的;它是一个纯函数调用,因此对
mark
sieve
的递归调用会立即调用。将最外层的调用包装到
sieve
中的
lazy seq
只用于延迟初始调用;它不会使整个序列变懒。相反,对
cons
的每个调用必须包装在自己的延迟序列中

例如:

(defn eager-iterate [f x]
  (cons x (eager-iterate f (f x))))

(take 3 (eager-iterate inc 0)) ; => StackOverflowError
(take 3 (lazy-seq (eager-iterate inc 0))) ; => Still a StackOverflowError
将其与迭代的实际源代码进行比较:

(defn iterate
  "Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects"
  {:added "1.0"
   :static true}
  [f x] (cons x (lazy-seq (iterate f (f x)))))
总而言之,这里有一个
mark
的实现,它对有限序列正确工作,对无限序列保持惰性。固定
筛子
留给读者作为练习

(defn mark [[x :as xs] k m]
  (lazy-seq
    (when (seq xs)
      (if (= k m)
        (cons 0 (mark (next xs) 1 m))
        (cons x (mark (next xs) (inc k) m))))))

(mark (range 4 14) 1 3)
; => (4 5 0 7 8 0 10 11 0 13)
(take 10 (mark (iterate inc 4) 1 3))
; => (4 5 0 7 8 0 10 11 0 13)

感谢@Alex的回答,我终于想出了一个可行的解决方案:

;; Helper function for marking mutiples of a number as 0
(defn mark [[x :as xs] k m]
  (lazy-seq
    (when-not (empty? xs)
      (if (= k m)
        (cons 0 (mark (rest xs) 1 m))
        (cons x (mark (rest xs) (inc k) m))))))


;; Sieve of Eratosthenes
(defn sieve
  [[x :as xs]]
  (lazy-seq
    (when-not (empty? xs)
      (if (= x 0)
        (sieve (rest xs))
        (cons x (sieve (mark (rest xs) 1 x)))))))

其他人建议我使用
rest
而不是
next

你的递归没有基本情况我已经完成了你留下的练习。谢谢正是这些答案应该在“解决方案”中共享,+1用于提高对尾部呼叫优化的认识,这是我最初的“解决方案”思想,只是看看问题和示例。。。。