我现在正在学习懒惰的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
是否会吞噬堆叠帧?如果没有,它是如何避免的呢?
答案 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))))
正如您所看到的那样,迭代函数调用被包装在另一个函数中(这使得它变得懒惰,即函数将在稍后调用)。