lazy-seq中的(list)导致无限递归,但(cons)不会

时间:2014-08-02 21:37:26

标签: clojure lazy-sequences

在试图理解lazy-seq时,我提出了这个例子:

(defn zeroes []
  (list 0 (lazy-seq (zeroes))))

(take 5 (zeroes)) ; too much recursion error

然而,这会引发过多的递归错误。用(cons)替换(列表)可以解决问题,但我不明白为什么:

(defn zeroes []
  (cons 0 (lazy-seq (zeroes))))

(take 5 (zeroes))  ; returns (0 0 0 0 0)

我对lazy-seq的理解是它立即返回一个lazy-seq实例,但是直到调用first或rest在该实例上才会评估它的主体。所以我认为(零)会返回一个简单的Cons of 0和一个LazySeq,而且还有一个尚未评估的身体。

作为一个额外的好奇心,我很困惑为什么这会挂起repl(因为repl尝试打印无限序列)但不会触发“过多的递归”错误。

(defn zeroes []
  (cons 0 (lazy-seq (zeroes))))

(zeroes)  ; hangs the repl

(如果相关,我正在http://himera.herokuapp.com/index.html的ClojureScript repl中尝试这些示例。)

1 个答案:

答案 0 :(得分:1)

你问了两个问题......

  

1)为什么在以下代码中使用list而不是cons会导致无限递归?

(defn zeroes []
  (cons 0 (lazy-seq (zeroes))))

(take 5 (zeroes)) ; too much recursion error

使用cons的版本产生无限的零序列,如下所示:

(0 0 0 0 ...)

如果您改用list,则会产生完全不同的结果。您可以无限地嵌套两个元素的列表( head = 0 tail =另一个列表):

'(0 (0 (0 (0 (...))))

由于顶级列表只有两个元素,因此当您调用(take 5)时,最终会得到整个。你得到了太多的递归"当REPL尝试打印出这些无限嵌套列表时出错。

请注意,您可以安全地将list*替换为conslist*函数采用可变数量的参数(list也是如此),但与list不同,它假设最后一个参数是 seq 。这意味着(list* a b c d)基本上只是(cons a (cons b (cons c d)))的简写。由于(list* a b)(cons a b)基本相同,因此您可以在代码中进行替换。在这种情况下,它可能没有多大意义,但如果您同时cons多个项目,那就太好了。


  

2)为什么以下分歧(挂起)而不是抛出过多的递归"我们在上面看到的错误?

(defn zeroes []
  (cons 0 (lazy-seq (zeroes))))

(zeroes)  ; hangs the repl

zeros函数产生一个" flat"零序列(与上面的嵌套列表不同)。 REPL可能使用尾递归函数来评估序列的每个连续的惰性元素。尾调用优化允许递归函数永远重复,而不会吹掉调用堆栈 - 这样就会发生什么。 Clojure中的递归尾调用recur special form表示。