Lazy seqs are a way to defer computation of actual seq elements, but those elements do need to be computed eventually. That doesn't actually have to involve cons
– for example clojure.core/concat
uses "chunked conses" when processing chunked operands, and it's ok to wrap any concrete seq type whatsoever in lazy-seq
– but some kind of non-lazy return after however many layers of lazy-seq
is necessary if any seq processing is to take place. Otherwise there won't even be a first element to get to.
Put yourself in the position of a function that's been handed a lazy seq. The caller has told it, in effect, "here's this thing that's for all intents and purposes a seq, but I don't feel like computing the actual elements until later". Now our function needs some actual elements to operate, so it pokes and prods the seq to try and get it to produce some elements… and then what?
If peeling off some lazy-seq
layers eventually produces a Cons
cell, a list, a seq over a vector or any other concrete seq-like thing with actual elements, then great, the function can read off an element from that and make progress.
But if the only result of peeling off those layers is that more layers are revealed, and it's lazy-seq
s all the way down, well… There are no elements to be found. And since in principle there is no way to determine whether by peeling off sufficiently many layers some elements could eventually be produced (cf. the halting problem), the function consuming an unrealizable lazy seq of this sort has in general no choice but to continue looping forever.
To take another angle, let's consider your even-numbers-v2
function. It takes an argument and returns a lazy-seq
object wrapping a further call to itself. Now, the original argument it receives (n
) is used to compute the argument to the recursive call ((+ 2 n)
), but otherwise isn't placed in any data structure or otherwise conveyed to the caller, so there is no reason why it would occur as an element of the resulting seq. All the caller sees is that the function has produced a lazy seq object and it has no choice but to unwrap that in search for an actual element of the sequence; and of course then the situation repeats itself (not strictly forever in this case, but only because +
will eventually complain about arithmetic overflow when dealing with longs).
take 10
, the lazy-seq thunk is called. It yields only another lazy-seq thunk which is also called. It yields only another lazy-seq thunk... If instead these were yielding an element and optionally another lazy-seq thunk (or an [empty] seq), then eventually this would stop once 10 were found of the sequence terminated. – A. Webb