Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

lazy-seq -- cons outside or in

Tags:

clojure

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?

like image 337
event_jr Avatar asked Jun 09 '13 17:06

event_jr


People also ask

What does cons do in Clojure?

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.)

What is a lazy sequence in Clojure?

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.


2 Answers

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.

like image 104
amalloy Avatar answered Oct 09 '22 00:10

amalloy


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).

like image 24
Michał Marczyk Avatar answered Oct 09 '22 00:10

Michał Marczyk