Should cons be inside (lazy-seq ...)
(def lseq-in (lazy-seq (cons 1 (more-one))))
or out?
(def lseq-out (cons 1 (lazy-seq (more-one))))
I noticed
(realized? lseq-in)
;;; ⇒ false
(realized? lseq-out)
;;; ⇒ <err>
;;; ClassCastException clojure.lang.Cons cannot be cast to clojure.lang.IPending clojure.core/realized? (core.clj:6773)
All the examples on the clojuredocs.org use "out".
What are the tradeoffs involved?
A Clojure Cons takes an arbitrary Object as head, and an ISeq as tail. Since Cons itself implements ISeq , you can build sequences out of Cons cells, but they can just as well point to vectors, or lists, etc. (Note that a "list" in Clojure is a special type ( PersistentList ), and is not built from Cons objects.)
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. Evaluation of lazy sequences is known as realization.
You definitely want (lazy-seq (cons ...))
as your default, deviating only if you have a clear reason for it. clojuredocs.org
is fine, but the examples are all community-provided and I would not call them "the docs". Of course, a consequence of how it's built is that the examples tend to get written by people who just learned how to use the construct in question and want to help out, so many of them are poor. I would refer instead to the code in clojure.core, or other known-good code.
Why should this be the default? Consider these two implementations of map
:
(defn map1 [f coll]
(when-let [s (seq coll)]
(cons (f (first s))
(lazy-seq (map1 f (rest coll))))))
(defn map2 [f coll]
(lazy-seq
(when-let [s (seq coll)]
(cons (f (first s))
(map2 f (rest coll))))))
If you call (map1 prn xs)
, then an element of xs will be realized and printed immediately, even if you never intentionally realize an element of the resulting mapped sequence. map2
, on the other hand, immediately returns a lazy sequence, delaying all its work until an element is requested.
With cons
inside lazy-seq
, the evaluation of the expression for the first element of your seq gets deferred; with cons
on the outside, it's done right away and only the construction of the "rest" part of the seq is deferred. (So (rest lseq-out)
will be a lazy seq.)
Thus, if computing the first element is expensive and it might not be needed at all, putting cons
inside lazy-seq
makes more sense. If the initial element is supplied to the lazy seq producer as an argument, it may make more sense to use cons
on the outside (this is the case with clojure.core/iterate
). Otherwise it doesn't make that much of a difference. (The overhead of creating a lazy seq object at the start is negligible.)
Clojure itself uses both approaches (although in the majority of cases lazy-seq
wraps the whole seq-producing expression, which may not necessarily start with cons
).
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