Support for using
realized? with lazy sequences has landed in ClojureScript.
What kind of new fun can we have with this capability?
First, let's say we have a lazy sequence. Here's one representing the factors for each natural number:
(def factors (map (fn [n] (filter (fn [x] (zero? (rem n x))) (range 1 (inc n)))) (rest (range))))
This sequence looks like
((1) (1 2) (1 3) (1 2 4) (1 5) ...)
Even though the
def defines an infinite sequence, defining it at the REPL simply results in the var
#'factors being returned, but with none of the factors yet being calculated.
You can verify this:
(realized? factors) yields
If you evaluate
(first factors), you will get
(1). After doing this,
(realized? factors) will yield
true which means that the first item has been realized. And you can confirm that only the first item has been realized:
(realized? (rest factors)) yields
Now, let's say we do something like,
(take 4 factors), causing the next 3 items of this infinite sequence to be calculated and cached. A function like the following (due to Alan Malloy) can be used to calculate how much of the infinite sequence has been realized:
(defn realized-length [xs] (loop [n 0 xs xs] (if (realized? xs) (recur (inc n) (rest xs)) n)))
(realized-length factors) produces the desired result of
Adding support for
realized? for lazy sequences was relatively easy in ClojureScript. At its core,
realized? simply needs its argument to be of a type that satisfies
cljs.core/IPending. This involves providing an implementation for its
In ClojureScript, lazy sequences are implemented by a
deftype with a
fn field which is used to realize the value. After this
fn has been used, it is set to
nil (it is marked as
^:mutable), and, via this side effect, we can deduce that the value has been realized.
This strategy is in fact analogous to the one used in
Delay—the type that underlies the
delay function—and its existing support for
realized? in ClojureScript.
The implementation landed for
LazySeq simply mimics the approach used for
Delay, checking to see if the
fn mutable field has been set to
nil in the inline
IPending implementation in the
IPending (-realized? [x] (not fn))
That's all there is to it!