Search code examples
clojurelazy-sequences

Find the elements of a LazySeq that have been realized


I have a LazySeq of connections that are created when realized. If an exception occurs while attempting to create a connection, I'd like to iterate through all of the connections that have already been realized in the LazySeq and close them. Something like:

(try  
  (dorun connections)
  (catch ConnectException (close-connections connections)))

This doesn't quite work though since close-connections will attempt to realize the connections again. I only want to close connections that have been realized, not realize additional connections. Any ideas for doing this?


Solution

  • Code:

    This returns the previously realized initial fragment of the input seq as a vector:

    (defn take-realized [xs]
      (letfn [(lazy-seq? [xs]
                (instance? clojure.lang.LazySeq xs))]
        (loop [xs  xs
               out []]
          (if (or (and (lazy-seq? xs) (not (realized? xs)))
                  (and (not (lazy-seq? xs)) (empty? xs)))
            out
            (recur (rest xs) (conj out (first xs)))))))
    

    Testing at the REPL:

    (defn lazy-printer [n]
      (lazy-seq
       (when-not (zero? n)
         (println n)
         (cons n (lazy-printer (dec n))))))
    
    (take-realized (lazy-printer 10))
    ;= []
    
    (take-realized (let [xs (lazy-printer 10)] (dorun (take 1 xs)) xs))
    ;=> 10
    ;= [10]
    
    ;; range returns a lazy seq...
    (take-realized (range 20))
    ;= []
    
    ;; ...wrapping a chunked seq
    (take-realized (seq (range 40)))
    ;= [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
    ;   17 18 19 20 21 22 23 24 25 26 27 28 29 30 31]
    
    ;; NB. *each* chunk of range gets its own LazySeq wrapper,
    ;; so that it is possible to work with infinite (or simply huge) ranges
    

    (Using ;=> to indicate a printout.)

    Discussion:

    realized? is indeed the way to go, as suggested by Nathan. However, as I explained in my comments on Nathan's answer, one must also make sure that one doesn't inadvertently call seq on the one's input, as that would cause the previously-unrealized fragments of the input seq to become realized. That means that functions such as non-empty and empty? are out, since they are implemented in terms of seq.

    (In fact, it is fundamentally impossible to tell whether a lazy seq is empty without realizing it.)

    Also, while functions like lazify are useful for unchunking sequences, they do not prevent their underlying seqs from being realized in a chunked fashion; rather, they enable layers of processing (map, filter etc.) to operate in an unchunked fashion even while their original input seqs are chunked. There is in fact no connection at all between such "lazified" / "unchunked" seq being realized and its underlying, possibly chunked seq being realized. (In fact there is no way to establish such a connection in the presence of other observers of the input seq; absent other observers, it could be accomplished, but only at the cost of making lazify considerably more tedious to write.)