4
;; create lazy infinite seq of random ints
(def infi-seq (repeatedly #(rand-int 11)))
(take 5 infi-seq) ; => (8 2 9 9 5)

How can I get all values realized (8 2 9 9 2) without knowing that the first five values have been realized?

deadghost
  • 5,017
  • 3
  • 34
  • 46
  • Do you want to generate a list of 5 random values every time? "without knowing that the first five values have been realized" what do you mean? – Mark Karpov Aug 27 '14 at 14:59
  • 1
    @Mark I don't want to generate a list of 5 random values every time. `infi-seq` defers evaluations of its expressions until they are needed. In this case five expressions are evaluated and the values cached. The rest of `infi-seq` is unevaluated until needed. I want to get all the evaluated/cached(realized) values of `infi-seq`. I hope that was a passable explanation. – deadghost Aug 27 '14 at 15:19
  • Yes, now it's clear. +1 – Mark Karpov Aug 27 '14 at 15:23

3 Answers3

4

This should do the trick:

(defn take-realized
  [coll]
  (if-not (instance? clojure.lang.IPending coll)
    (cons (first coll) (take-realized (rest coll)))
    (when (realized? coll)
       (cons (first coll) (take-realized (rest coll))))))
DanLebrero
  • 8,545
  • 1
  • 29
  • 30
  • Sometimes fails: `(take-realized [0 1 2 3 4])` produces `ClassCastException clojure.lang.PersistentVector$ChunkedSeq cannot be cast to clojure.lang.IPending`. A defect in chunking? – Thumbnail Aug 27 '14 at 16:04
  • What's the purpose of `lazy-seq` here as it seems to work without it? – deadghost Aug 27 '14 at 16:04
  • @Thumbnail Perhaps `realized?` only takes deferred values? – deadghost Aug 27 '14 at 16:08
  • Right, `realized?` only works on instances of IPending. Thus `(take-realized (iterate inc 1))` would also fail due to `iterate` consing the first element without a `lazy-seq` wrapper. – A. Webb Aug 27 '14 at 16:16
  • @deadghost I think it's a defect in `realized?`, which purportedly *Returns true if a value has been produced for a promise, delay, future or lazy sequence*. As [A.Webb's answer](http://stackoverflow.com/a/25532104/1562315) takes account of, it explodes with anything other than a `clojure.lang.IPending` argument. – Thumbnail Aug 27 '14 at 16:23
  • I thought you wanted this only for lazy seqs. I added a check for the IPending, but as A. Webb points out it will not work for iterate – DanLebrero Aug 27 '14 at 16:23
  • Note quite there yet, now `(take-realized (iterate inc 1))` is going to return the entire lazy sequence. Iterate is a bit of an odd-ball debated a bit on a [related question on the Google group](https://groups.google.com/forum/#!msg/clojure/5rwZA-Bzp9A/dgChQhbeF_AJ). – A. Webb Aug 27 '14 at 16:26
  • Overflows with `(take-realized [0 1 2 3 4])` as there's no terminating condition in the event that `coll` is not `clojure.lang.IPending` but as is it answers my question as I only wanted values from a lazy seq. – deadghost Aug 27 '14 at 21:33
  • @deadghost You can add a `seq` test and recast the code as follows: `(defn take-realized [coll] (let [test (if (instance? clojure.lang.IPending coll) realized? seq)] (when (test coll) (cons (first coll) (take-realized (rest coll))))))` – Thumbnail Aug 28 '14 at 03:24
  • @Thumbnail That won't quite work for lazy-sequences that terminate. `(take-realized (doall (repeat 5 5))) ;=> (5 5 5 5 5 nil)`. – A. Webb Aug 28 '14 at 14:32
  • @A.Webb Yes. You explained this for your own `count-realized`. The comment both inadequately repairs the logic and recasts the code: better kept apart. – Thumbnail Aug 30 '14 at 10:09
2

You can modify or use count-realized

(defn count-realized [s] 
  (loop [s s, n 0] 
    (if (instance? clojure.lang.IPending s)
      (if (and (realized? s) (seq s))
        (recur (rest s) (inc n))
        n)
      (if (seq s)
        (recur (rest s) (inc n))
        n))))

Note the check for IPending is necessary for cases where non-lazy elements prepend the sequence, as in iterate.


(def foo (iterate inc 1))

(take (count-realized foo) foo)
;=> (1)

(dorun (take 5 foo))
;=> nil

(take (count-realized foo) foo)
;=> (1 2 3 4 5)
Community
  • 1
  • 1
A. Webb
  • 26,227
  • 1
  • 63
  • 95
  • Do you have to check `realized?` before `seq` for a `clojure.lang.IPending` because `seq` might have (and, in the case of `iterate`, does have) the side effect of realizing its argument? – Thumbnail Aug 28 '14 at 02:49
  • @Thumbnail Right, `seq` forces realization. There is no way to check a lazy-sequence for emptiness without realizing a possible first element. – A. Webb Aug 28 '14 at 03:26
  • Is the `seq` test necessary? If it's realized, it has an element. dAni's answer induced this thought. – Thumbnail Aug 28 '14 at 03:31
  • @Thumbnail Yes, it is necessary. No, a realized sequence does not necessarily have an element. Lazy sequences that terminate do so with an empty tail. `(let [bar (doall (lazy-seq nil))] ((juxt realized? empty?) bar)) ;=> [true true]` – A. Webb Aug 28 '14 at 03:44
1

Perhaps this:

(defn take-realized [coll]
  (map first (take-while realized? (iterate rest coll))))

Neat but meretricious. It can be fixed to some extent:

(defn take-realized [coll]
  (->> coll
       (iterate next)
       (take-while #(and % (or (not (instance? clojure.lang.IPending %)) (realized? %))))
       (map first)))

But this still produces an infinite lazy sequence with iterate:

(take 5 (take-realized (iterate inc 0)))
;(0 1 2 3 4) 

The other answers are superior.

Thumbnail
  • 13,293
  • 2
  • 29
  • 37