When processing each element in a seq
I normally use first and rest.
However these will cause a lazy-seq
to lose its "laziness" by calling seq
on the argument. My solution has been to use (first (take 1 coll))
and (drop 1 coll)
in their place when working with lazy-seq
s, and while I think drop 1
is just fine, I don't particularly like having to call first
and take
to get the first element.
Is there a more idiomatic way to do this?
Lazy Sequences in Clojure Clojure reference explains laziness as: Most of the sequence library functions are lazy, i.e. functions that return seqs do so incrementally, as they are consumed, and thus consume any seq arguments incrementally as well.
Clojure is not a lazy language. However, Clojure supports lazily evaluated sequences. This means that sequence elements are not available ahead of time and produced as the result of a computation. The computation is performed as needed.
Clojure defines many algorithms in terms of sequences (seqs). A seq is a logical list, and unlike most Lisps where the list is represented by a concrete, 2-slot structure, Clojure uses the ISeq interface to allow many data structures to provide access to their elements as sequences.
The docstrings for first
and rest
say that these functions call seq
on their arguments to convey the idea that you don't have to call seq
yourself when passing in a seqable collection which is not in itself a seq, like, say, a vector or set. For example,
(first [1 2 3])
;= 1
would not work if first
didn't call seq
on its argument; you'd have to say
(first (seq [1 2 3]))
instead, which would be inconvenient.
Both take
and drop
also call seq
on their arguments, otherwise you couldn't call them on vectors and the like as explained above. In fact this is true of all standard seq collections -- those which do not call seq
directly are built upon lower-level components which do.
In no way does this impair the laziness of lazy seqs. The forcing / realization which happens as a result of a first
/ rest
call is the smallest amount possible to obtain the requested result. (How much that is depends on the type of the argument; if it is not in fact lazy, there is no extra realization involved in the first
call; if it is partly lazy -- that is, chunked -- there will be some extra realization (up to 32 initial elements will be computed at once); if it's fully lazy, only the first element will be computed.)
Clearly first
, when passed a lazy seq, must force the realization of its first element -- that's the whole point. rest
is actually somewhat lazy in that it actually doesn't force the realization of the "rest" part of the seq (that's in contrast to next
, which is basically equivalent to (seq (rest ...))
). The fact that it does force the first element to be realized so that it can skip over it immediately is a conscious design choice which avoids unnecessary layering of lazy seq objects and holding the head of the original seq; you could say something like (lazy-seq (rest xs))
to defer even this initial realization, at the cost of holding on to xs
until realized the lazy seq wrapper is realized.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With