在试图理解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中尝试这些示例。)
答案 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*
替换为cons
。 list*
函数采用可变数量的参数(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表示。