Clojure (list)在lazy seq中导致无限递归,但(cons)不会
在试图理解lazy seq时,我给出了以下示例:Clojure (list)在lazy seq中导致无限递归,但(cons)不会,clojure,lazy-sequences,Clojure,Lazy Sequences,在试图理解lazy seq时,我给出了以下示例: (defn zeroes [] (list 0 (lazy-seq (zeroes)))) (take 5 (zeroes)) ; too much recursion error 但是,这会触发过多的递归错误。用(cons)替换(list)修复了问题,但我不明白为什么: (defn zeroes [] (cons 0 (lazy-seq (zeroes)))) (take 5 (zeroes)) ; returns (0 0 0
(defn zeroes []
(list 0 (lazy-seq (zeroes))))
(take 5 (zeroes)) ; too much recursion error
但是,这会触发过多的递归错误。用(cons)替换(list)修复了问题,但我不明白为什么:
(defn zeroes []
(cons 0 (lazy-seq (zeroes))))
(take 5 (zeroes)) ; returns (0 0 0 0 0)
我对lazy-seq的理解是,它立即返回一个lazy-seq实例,但在调用该实例上的first或rest之前,不会对其主体进行求值。所以我认为(零)只会返回一个简单的Cons 0和一个LazySeq,还有一个未赋值的主体
另外一个奇怪的问题是,我不明白为什么这会挂起repl(因为repl试图打印无限序列),但不会触发“太多递归”错误
(defn zeroes []
(cons 0 (lazy-seq (zeroes))))
(zeroes) ; hangs the repl
(如果相关的话,我会在ClojureScript回复中尝试这些例子。)你问了两个问题。。。
1) 为什么在下面的代码中使用list
而不是cons
会导致无限递归
使用cons
的版本会产生一个无限的零序列,如下所示:
(0 0 0 0 ...)
如果改用list
,则会产生完全不同的结果。您将得到一个无限嵌套的列表,每个列表包含两个元素(head=0,tail=另一个列表):
由于顶级列表只有两个元素,因此在调用(取5)
时,您将得到整个列表。当REPL试图打印出这些无限嵌套的列表时,会出现“太多递归”错误
请注意,您可以安全地用替换cons
。list*
函数接受数量可变的参数(与list
一样),但与list
不同,它假定最后一个参数是seq。这意味着(list*abcd)
本质上只是(cons a(cons b(cons cd))
的简写。由于(list*ab)
与(cons a b)
基本相同,因此可以在代码中进行替换。在这种情况下,这可能没有多大意义,但如果您同时对多个项目进行cons
-ing操作,这就很好了
2) 为什么下面的代码会发散(挂起),而不是像上面看到的那样抛出“太多递归”错误
zeros
函数生成一个“平坦”的零序列(与上面的嵌套列表不同)。REPL可能使用尾部递归函数来计算序列中每个连续的惰性元素。尾部调用优化允许递归函数在不破坏调用堆栈的情况下永远重复出现,因此这就是发生的情况。Clojure中的递归尾部调用由.表示,谢谢。我确实发现我误解了cons/list的区别以及lazy seq的身体应该返回什么。我很好奇,为什么list*返回的是Cons而不是PersistentList时会这样命名。PersistentList的节点链是否可能以序列结束?或者只有Cons才能这样做?名称list*
只是传统。我认为所有其他Lisp/Scheme变体都有类似的功能,和。
(0 0 0 0 ...)
'(0 (0 (0 (0 (...))))
(defn zeroes []
(cons 0 (lazy-seq (zeroes))))
(zeroes) ; hangs the repl