From lazy seqs to reducers and back
Sometimes in a seq pipeline, you know that some intermediate results are, well, intermediate and as such don’t need to be persistent but, on the whole, you still need the laziness.
You can’t always opt for reducers because while being non-persistent (and thus faster), they imply getting rid of laziness.
So you can’t mix and match transformations on sequences and on reducers when you care for laziness. Or, wait!, maybe you can.
At core, reducers are just a clever way to compose big functions while giving the user the illusion of manipulating collections. So we should be able to apply a reducer pipeline if we get hold of the composed function. This is exactly what the below seq-seq
function does:
(defn reverse-conses ([s tail] (if (identical? (rest s) tail) s (reverse-conses s tail tail))) ([s from-tail to-tail] (loop [f s b to-tail] (if (identical? f from-tail) b (recur (rest f) (cons (first f) b)))))) (defn seq-seq [f s] (let [f1 (reduce #(cons %2 %1) nil (f (reify clojure.core.protocols.CollReduce (coll-reduce [this f1 init] f1))))] ((fn this [s] (lazy-seq (when-let [s (seq s)] (let [more (this (rest s)) x (f1 more (first s))] (if (reduced? x) (reverse-conses @x more nil) (reverse-conses x more)))))) s))) (defmacro seq->> [s & forms] `(seq-seq (fn [n#] (->> n# ~@forms)) ~s))
Note that the captured function (f1
) may be impure, so don’t share it!
Now one can specifies non-persistent lazy seq pipelines:
=> (seq->> (range) (r/map str) (r/take 25) (r/drop 5)) ("5" "6" "7" "8" "9" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "20" "21" "22" "23" "24")
To prove laziness is at play here:
=> (take 2 (seq->> (range) (r/map #(str (doto % prn))) (r/take 25) (r/drop 5))) (0 1 2 3 4 5 6 "5" "6")
Of course seq-seq
could be made to support chunked sequences and, if you try to play with it, beware of r/mapcat
.
Update:
I added reverse-conses
to account for the fact that when several items are added during a single call to f1
(eg during a r/mapcat
“step”) they were added in the wrong order – if only everything was more set-like.
tumi 価格
situs slot terpercaya
Clojure and me » From lazy seqs to reducers and back