Skip to content

Commit f5e8fa9

Browse files
committed
CLJS-3353: Add the new iteration function introduced in Clojure 1.11
1 parent c9a7e0d commit f5e8fa9

2 files changed

Lines changed: 143 additions & 0 deletions

File tree

src/main/cljs/cljs/core.cljs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10944,6 +10944,49 @@ reduces them without incurring seq initialization"
1094410944
(reduce #(proc %2) nil coll)
1094510945
nil)
1094610946

10947+
(defn iteration
10948+
"Creates a seqable/reducible via repeated calls to step,
10949+
a function of some (continuation token) 'k'. The first call to step
10950+
will be passed initk, returning 'ret'. Iff (somef ret) is true,
10951+
(vf ret) will be included in the iteration, else iteration will
10952+
terminate and vf/kf will not be called. If (kf ret) is non-nil it
10953+
will be passed to the next step call, else iteration will terminate.
10954+
This can be used e.g. to consume APIs that return paginated or batched data.
10955+
step - (possibly impure) fn of 'k' -> 'ret'
10956+
:somef - fn of 'ret' -> logical true/false, default 'some?'
10957+
:vf - fn of 'ret' -> 'v', a value produced by the iteration, default 'identity'
10958+
:kf - fn of 'ret' -> 'next-k' or nil (signaling 'do not continue'), default 'identity'
10959+
:initk - the first value passed to step, default 'nil'
10960+
It is presumed that step with non-initk is unreproducible/non-idempotent.
10961+
If step with initk is unreproducible it is on the consumer to not consume twice."
10962+
{:added "1.11"}
10963+
[step & {:keys [somef vf kf initk]
10964+
:or {vf identity
10965+
kf identity
10966+
somef some?
10967+
initk nil}}]
10968+
(reify
10969+
ISeqable
10970+
(-seq [_]
10971+
((fn next [ret]
10972+
(when (somef ret)
10973+
(cons (vf ret)
10974+
(when-some [k (kf ret)]
10975+
(lazy-seq (next (step k)))))))
10976+
(step initk)))
10977+
IReduce
10978+
(-reduce [_ rf init]
10979+
(loop [acc init
10980+
ret (step initk)]
10981+
(if (somef ret)
10982+
(let [acc (rf acc (vf ret))]
10983+
(if (reduced? acc)
10984+
@acc
10985+
(if-some [k (kf ret)]
10986+
(recur acc (step k))
10987+
acc)))
10988+
acc)))))
10989+
1094710990
(defprotocol IEncodeJS
1094810991
(-clj->js [x] "Recursively transforms clj values to JavaScript")
1094910992
(-key->js [x] "Transforms map keys to valid JavaScript keys. Arbitrary keys are

src/test/cljs/cljs/seqs_test.cljs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
(ns cljs.seqs-test
1010
(:refer-clojure :exclude [iter])
1111
(:require [cljs.test :refer-macros [deftest testing is]]
12+
[clojure.test.check :as tc]
13+
[clojure.test.check.clojure-test :refer-macros [defspec]]
14+
[clojure.test.check.generators :as gen]
15+
[clojure.test.check.properties :as prop :include-macros true]
16+
[clojure.test.check.random :as random]
1217
[clojure.string :as s]
1318
[clojure.set :as set]))
1419

@@ -237,3 +242,98 @@
237242
(is (true? (js-iterable? (js/Set.))))
238243
(is (false? (js-iterable? 1)))
239244
(is (false? (js-iterable? nil)))))
245+
246+
(deftest test-iteration-opts
247+
(let [genstep (fn [steps]
248+
(fn [k] (swap! steps inc) (inc k)))
249+
test (fn [expect & iteropts]
250+
(is (= expect
251+
(let [nsteps (atom 0)
252+
iter (apply iteration (genstep nsteps) iteropts)
253+
ret (doall (seq iter))]
254+
{:ret ret :steps @nsteps})
255+
(let [nsteps (atom 0)
256+
iter (apply iteration (genstep nsteps) iteropts)
257+
ret (into [] iter)]
258+
{:ret ret :steps @nsteps}))))]
259+
(test {:ret [1 2 3 4]
260+
:steps 5}
261+
:initk 0 :somef #(< % 5))
262+
(test {:ret [1 2 3 4 5]
263+
:steps 5}
264+
:initk 0 :kf (fn [ret] (when (< ret 5) ret)))
265+
(test {:ret ["1"]
266+
:steps 2}
267+
:initk 0 :somef #(< % 2) :vf str))
268+
269+
;; kf does not stop on false
270+
(let [iter #(iteration (fn [k]
271+
(if (boolean? k)
272+
[10 :boolean]
273+
[k k]))
274+
:vf second
275+
:kf (fn [[k v]]
276+
(cond
277+
(= k 3) false
278+
(< k 14) (inc k)))
279+
:initk 0)]
280+
(is (= [0 1 2 3 :boolean 11 12 13 14]
281+
(into [] (iter))
282+
(seq (iter))))))
283+
284+
(deftest test-iteration
285+
;; equivalence to es6-iterator-seq
286+
(let [arr #js [1 nil 3 true false 4 6 nil 7]]
287+
(is (= (let [iter (es6-iterator arr)]
288+
(vec (iteration (fn [_] (.next iter))
289+
:somef #(not (.-done %))
290+
:vf #(.-value %))))
291+
(let [iter (es6-iterator arr)]
292+
(vec (es6-iterator-seq iter))))))
293+
294+
;; paginated API
295+
(let [items 12 pgsize 5
296+
src (vec (repeatedly items #(random-uuid)))
297+
api (fn [tok]
298+
(let [tok (or tok 0)]
299+
(when (< tok items)
300+
{:tok (+ tok pgsize)
301+
:ret (subvec src tok (min (+ tok pgsize) items))})))]
302+
(is (= src
303+
(mapcat identity (iteration api :kf :tok :vf :ret))
304+
(into [] cat (iteration api :kf :tok :vf :ret)))))
305+
306+
(let [src [:a :b :c :d :e]
307+
api (fn [k]
308+
(let [k (or k 0)]
309+
(if (< k (count src))
310+
{:item (nth src k)
311+
:k (inc k)})))]
312+
(is (= [:a :b :c]
313+
(vec (iteration api
314+
:somef (comp #{:a :b :c} :item)
315+
:kf :k
316+
:vf :item))
317+
(vec (iteration api
318+
:kf #(some-> % :k #{0 1 2})
319+
:vf :item))))))
320+
321+
(defn- make-rng [seed]
322+
(atom (random/make-random seed)))
323+
324+
(defn- next-long [rng]
325+
(let [[r1 r2] (random/split @rng)]
326+
(reset! rng r2)
327+
(long (random/rand-long r1))))
328+
329+
(defspec iteration-seq-equals-reduce 1000
330+
(prop/for-all [initk gen/small-integer
331+
seed gen/small-integer]
332+
(let [src (fn []
333+
(let [rng (make-rng seed)]
334+
(iteration #(unchecked-add % (next-long rng))
335+
:somef (complement #(zero? (mod % 1000)))
336+
:vf str
337+
:initk initk)))]
338+
(= (into [] (src))
339+
(into [] (seq (src)))))))

0 commit comments

Comments
 (0)