Lazy sequences and realization

spork-lang 0.6.0

Generator-backed transformations, finite realization, and effect-only consumption.

Lazy sequence functions defer work by returning Python generators. This page distinguishes lazy transformations from realization helpers and eager persistent results.

Lazy sequence functions#

These functions return Python generators unless a section states otherwise. Calling a generator function does not realize its result; use vec, doall, iteration, or a reducer to consume it. cycle, partition, partition-all, and reverse materialize their input when first consumed and therefore are not suitable for infinite inputs. sort and related helpers also realize their inputs but return concrete values.

map#

Applies a function to each element of one or more collections.

; Single collection
(map inc [1 2 3])              ; => (2 3 4)
(map str [1 2 3])              ; => ("1" "2" "3")

; Multiple collections (stops at shortest)
(map + [1 2 3] [10 20 30])     ; => (11 22 33)
(map + [1 2] [10 20 30])       ; => (11 22)
(map (fn [a b] [a b]) [1 2 3] [:a :b :c])
; => ([1 :a] [2 :b] [3 :c])

; With anonymous function
(map (fn [x] (* x x)) [1 2 3 4])  ; => (1 4 9 16)

; With keyword (extracts from maps)
(map :name [{:name "Alice"} {:name "Bob"}])  ; => ("Alice" "Bob")

filter#

Returns elements for which predicate returns true.

(filter even? [1 2 3 4 5 6])      ; => (2 4 6)
(filter odd? [1 2 3 4 5 6])       ; => (1 3 5)
(filter pos? [-2 -1 0 1 2])       ; => (1 2)
(filter #(isinstance % str) [1 "a" 2 "b"]) ; => ("a" "b")

; Filter with keyword (truthy values)
(filter :active [{:active true :name "A"}
                 {:active false :name "B"}
                 {:active true :name "C"}])
; => ({:active true :name "A"} {:active true :name "C"})

; Filter with set membership
(filter #(contains? #{2 4 6} %) [1 2 3 4 5 6]) ; => (2 4 6)

take#

Returns first n elements.

(take 3 [1 2 3 4 5])       ; => (1 2 3)
(take 10 [1 2 3])          ; => (1 2 3)
(take 0 [1 2 3])           ; => ()
(take 5 (range))           ; => (0 1 2 3 4)

take-while#

Returns elements while predicate is true, stops at first false.

(take-while pos? [1 2 3 0 -1 5])     ; => (1 2 3)
(take-while even? [2 4 6 7 8 10])    ; => (2 4 6)
(take-while #(< % 5) [1 2 3 4 5 6])  ; => (1 2 3 4)

drop#

Drops first n elements, returns rest.

(drop 2 [1 2 3 4 5])       ; => (3 4 5)
(drop 10 [1 2 3])          ; => ()
(drop 0 [1 2 3])           ; => (1 2 3)

drop-while#

Drops elements while predicate is true, returns rest.

(drop-while pos? [1 2 3 0 -1 5])     ; => (0 -1 5)
(drop-while even? [2 4 6 7 8 10])    ; => (7 8 10)
(drop-while #(< % 5) [1 2 3 4 5 6])  ; => (5 6)

concat#

Concatenates sequences together.

(concat [1 2] [3 4])           ; => (1 2 3 4)
(concat [1 2] [3 4] [5 6])     ; => (1 2 3 4 5 6)
(concat [1 2] nil [3 4])       ; => (1 2 3 4)
(concat "ab" "cd")             ; => ("a" "b" "c" "d")

repeat#

Returns a sequence of x repeated n times, using (repeat x n). Without n, the result is infinite.

(repeat "x" 3)              ; => ("x" "x" "x")
(repeat 0 5)                ; => (0 0 0 0 0)
(take 4 (repeat :a))        ; => (:a :a :a :a)
(vec (repeat [1 2] 3))      ; => [[1 2] [1 2] [1 2]]

cycle#

Returns an infinite cycle of collection elements.

(take 7 (cycle [1 2 3]))    ; => (1 2 3 1 2 3 1)
(take 5 (cycle [:a :b]))    ; => (:a :b :a :b :a)
(take 6 (cycle "ab"))       ; => ("a" "b" "a" "b" "a" "b")

iterate#

Returns infinite sequence: x, (f x), (f (f x)), ...

(take 5 (iterate inc 0))        ; => (0 1 2 3 4)
(take 5 (iterate #(* 2 %) 1))   ; => (1 2 4 8 16)
(take 4 (iterate rest [1 2 3])) ; => ([1 2 3] (2 3) (3) nil)

range#

Returns an integer range with Python's range semantics. With no arguments it is an infinite generator starting at zero.

(range 5)            ; => (0 1 2 3 4)
(range 1 5)          ; => (1 2 3 4)
(range 0 10 2)       ; => (0 2 4 6 8)
(range 10 0 -1)      ; => (10 9 8 7 6 5 4 3 2 1)
(take 5 (range))     ; => (0 1 2 3 4)

interleave#

Interleaves elements from multiple sequences. Stops at shortest.

(interleave [1 2 3] [:a :b :c])        ; => (1 :a 2 :b 3 :c)
(interleave [1 2] [:a :b :c])          ; => (1 :a 2 :b)
(interleave [1 2 3] [:a :b :c] ["x" "y" "z"])
; => (1 :a "x" 2 :b "y" 3 :c "z")

interpose#

Interposes separator between elements.

(interpose :sep [1 2 3])          ; => (1 :sep 2 :sep 3)
(interpose ", " ["a" "b" "c"])    ; => ("a" ", " "b" ", " "c")
(apply + (map str (interpose "-" [1 2 3]))) ; => "1-2-3"

partition#

Returns groups of n elements. The default step is n, and an incomplete final group is dropped. Optional step and pad arguments follow the collection; padding is emitted only if pad supplies enough values to complete the group.

(partition 2 [1 2 3 4 5 6])       ; => ([1 2] [3 4] [5 6])
(partition 2 [1 2 3 4 5])         ; => ([1 2] [3 4])
(partition 3 [1 2 3 4 5 6 7 8 9]) ; => ([1 2 3] [4 5 6] [7 8 9])

; A smaller step creates sliding windows
(partition 2 [1 2 3 4] 1)         ; => ([1 2] [2 3] [3 4])
(partition 3 [1 2 3 4 5] 1)       ; => ([1 2 3] [2 3 4] [3 4 5])

; A fourth argument supplies padding for the final group
(partition 3 [1 2 3 4] 3 [0 0])   ; => ([1 2 3] [4 0 0])

partition-all#

Like partition, but includes every incomplete group and does not take a padding argument.

(partition-all 2 [1 2 3 4 5])     ; => ([1 2] [3 4] [5])
(partition-all 3 [1 2 3 4 5])     ; => ([1 2 3] [4 5])
(partition-all 3 [1 2])           ; => ([1 2])

; Optional step follows the collection
(partition-all 3 [1 2 3 4] 1)     ; => ([1 2 3] [2 3 4] [3 4] [4])

keep#

Returns non-nil results of (f item).

(keep #(if (even? %) %) [1 2 3 4 5 6])  ; => (2 4 6)
(keep (fn [x] x) [1 nil 2 nil 3])      ; => (1 2 3)
(keep :name [{:name "A"} {} {:name "B"}])  ; => ("A" "B")

; Difference from filter: keep uses the RESULT of f
(keep #(if (pos? %) (* % 10)) [-1 0 1 2])  ; => (10 20)

keep-indexed#

Like keep but f receives index and item.

(keep-indexed #(if (even? %1) %2) [:a :b :c :d :e])
; => (:a :c :e)

(keep-indexed #(if (> %1 1) %2) [:a :b :c :d])
; => (:c :d)

map-indexed#

Like map but f receives index and item.

(map-indexed (fn [i x] [i x]) [:a :b :c])
; => ([0 :a] [1 :b] [2 :c])
(map-indexed #(.format "{}: {}" %1 %2) ["a" "b" "c"])
; => ("0: a" "1: b" "2: c")

(map-indexed (fn [i x] {:index i :value x}) [10 20 30])
; => ({:index 0 :value 10} {:index 1 :value 20} {:index 2 :value 30})

dedupe#

Removes consecutive duplicates.

(dedupe [1 1 2 2 3 1 1])     ; => (1 2 3 1)
(dedupe [1 2 3 4])           ; => (1 2 3 4)
(dedupe [:a :a :a :b :b :a]) ; => (:a :b :a)

distinct#

Removes duplicate hashable values, preserving their first occurrence. Unhashable Python objects are compared by identity rather than equality.

(distinct [1 2 1 3 2 4 3])   ; => (1 2 3 4)
(distinct [:a :b :a :c :b])  ; => (:a :b :c)
(distinct "abracadabra")     ; => ("a" "b" "r" "c" "d")

flatten#

Recursively flattens any nested iterable except strings and bytes. Maps therefore contribute keys through their normal iteration protocol.

(flatten [[1 2] [3 4]])              ; => (1 2 3 4)
(flatten [[1 [2 3]] [[4] 5]])        ; => (1 2 3 4 5)
(flatten [1 [2 [3 [4 [5]]]]])        ; => (1 2 3 4 5)
(flatten [1 2 3])                    ; => (1 2 3)

mapcat#

Maps over one or more collections and concatenates each non-nil result. It is equivalent to applying concat to the results of map.

(mapcat #(repeat % 2) [1 2 3])       ; => (1 1 2 2 3 3)
(mapcat reverse [[1 2] [3 4]])       ; => (2 1 4 3)
(mapcat #(range %) [1 2 3])          ; => (0 0 1 0 1 2)

; Useful for "expanding" each element
(mapcat (fn [x] [x (* x 10)]) [1 2 3])  ; => (1 10 2 20 3 30)

Sequence realization#

doall#

Consumes an iterable and returns its results as a persistent Vector.

(doall (map print [1 2 3]))      ; => [nil nil nil]
; also prints each value on its own line
(def realized (doall (map inc (range 5))))
realized                          ; => [1 2 3 4 5]

dorun#

Consumes an iterable and returns nil. Unlike doall, it does not retain the yielded values.

(dorun (map print [1 2 3]))      ; => nil
; also prints each value on its own line

realized?#

Distinguishes raw Python generator objects from concrete or sequence values. It returns false for a generator even after that generator has been partly or fully consumed; it does not track realization progress.

(def lazy-nums (map inc [1 2 3]))
(realized? lazy-nums)            ; => false
(first lazy-nums)                ; => 2
(realized? lazy-nums)            ; => false
(realized? (doall lazy-nums))    ; => true