是否使用`lazy-seq`递归占用堆栈帧?

时间:2014-05-28 06:04:22

标签: recursion clojure lazy-sequences

我现在正在学习懒惰的seqs,而且我注意到他们通常不使用recur就涉及递归。例如,以下是iterate

的实现
(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)))))

我的印象是,如果你在不使用recur的情况下递归,它将为每次调用创建一个新的堆栈帧,如果迭代次数足够,可能会导致堆栈溢出。

lazy-seq是否会吞噬堆叠帧?如果没有,它是如何避免的呢?

2 个答案:

答案 0 :(得分:4)

当你在这个例子中看到它包含一个迭代调用时,

lazy-seq实际上并没有递归。而是当您请求第二个项目时,lazy-seq宏将自己展开为暂停的调用。因此,每次检索项目时,都会导致一次调用,但旧调用不在堆栈中。

同样令人感兴趣的是你是否在消耗内存,这取决于你对序列头部的处理方式。如果保留var或local绑定,则将保留整个cons单元链。如果您避免握住头部并向下移动序列,GC可以在您身后清理。

一个棘手的问题是,lazy-seq宏中有一些特殊元素^ {:once true},它让编译器知道fn闭包是一次性的东西而不是保留任何关闭的变量。

答案 1 :(得分:1)

没有lazy-seq不会吞噬堆栈帧。

在这个代码的表面上,它可能看起来像一个递归调用但是lazy-seq不是一个函数,它是一个宏,在这种特殊情况下将转换为:

(lazy-seq (iterate f (f x))

变得类似

(new clojure.lang.LazySeq (fn [] (iterate f (f x))))

正如您所看到的那样,迭代函数调用被包装在另一个函数中(这使得它变得懒惰,即函数将在稍后调用)。