1 Introduction
1.1 Installation
Add [xyz.zcaudate/std.lib "4.1.5"] to project.clj dependencies.
1.2 Top Level
The top level functionality in std.lib.stream are:
produce: Converts different data types (like ranges, lists, or even functions) into a uniform sequence that can be processed.collect: Gathers the processed data into a desired output format, such as a vector, list, or a Java collection.pipeline: Creates a composite transducer from a series of individual transformation steps, enabling complex data manipulation.pipe: Connects a data source, a transformation pipeline, and a data sink to execute an entire stream operation.producer: Creates a lazy sequence from a source and a pipeline, allowing on-demand data generation.collector: Returns a function that, when given a source, collects the processed data into a specified sink after applying a pipeline.stream/*>: A versatile function/macro that intelligently acts as apipeline,producer,collector, orpipebased on the arguments provided, simplifying stream construction.
1.3 Produce
The produce function in std.lib.stream is designed to convert various data structures into a sequence, making them suitable for stream processing. Here are some examples of how it's used with different input types:
(require '[std.lib.stream :as s])
Producing from a range
(s/produce (range 5))
=> '(0 1 2 3 4)
(require '[std.lib.stream :as s])
(s/produce (fn [] 1))
;; If a function returns a sequence:
2 Walkthrough
2.1 Producing and collecting
produce turns different sources into a uniform sequence. collect consumes a sequence into a sink such as a vector, list, or Java collection.
produce a sequence from different sources
(s/produce (range 5))
=> '(0 1 2 3 4)
(s/produce [1 2 3])
=> [1 2 3]
(s/produce (fn [] 1))
=> '(1)
collect into various sinks
(s/collect [] (range 5))
=> [0 1 2 3 4]
(s/collect () (range 5))
=> '(4 3 2 1 0)
2.2 Pipelines and pipes
A pipeline is a composition of named transform steps. pipe wires a source, pipeline, and sink together in one call.
build and run a pipeline
(s/pipe (range 5)
[[:map inc]
[:map inc]]
[])
=> [2 3 4 5 6]
filter and take elements
(s/pipe (range 20)
[[:filter even?]
[:take 5]
[:map #(/ % 2)]]
[])
=> [0 1 2 3 4]
2.3 Producers and collectors
producer returns a lazy sequence; collector returns a reusable function. Combine them to build reusable stream stages.
create a lazy producer
(take 3 (s/producer (range 100)
[[:map inc]
[:filter odd?]]))
=> '(3 5 7)
create a reusable collector
(let [collect-odds (s/collector [[:filter odd?]] [])]
(collect-odds (range 10))
=> [1 3 5 7 9]
(collect-odds [[:map inc]] (range 5))
=> [2 4])
2.4 The stream shorthand
stream (and the *> macro) chooses between pipe, producer, collector, and pipeline based on which connectors are supplied. Use <*> for a missing connector.
use stream as a pipe
(s/stream (range 5)
[:map inc]
[])
=> [1 2 3 4 5]
use stream as a producer
(s/stream (range 5)
[:map inc]
(s/<*>))
=> seq?
use stream as a collector
(let [collect+1 (s/stream (s/<*>)
[:map inc])]
(collect+1 (range 5))
=> [1 2 3 4 5])
use the *> macro
(s/*> (range 5)
(s/produce (range 1 6))
[])
=> [1 2 3 4 5]
2.5 End-to-end: build a reusable data pipeline
A complete workflow: produce from a source, filter, transform, partition, and collect into a sink.
filter even numbers, pair them, and collect
(-> (s/pipe (range 10)
[[:filter even?]
[:partition-all 2]
[:map vec]]
[])
(conj [8 9]))
=> [[0 2] [4 6] [8]]
3 Summary and Examples
The std.lib.stream library provides a powerful and flexible way to process data by composing transformations into pipelines. It extends Clojure's sequence and transducer capabilities, allowing you to define how data flows from various sources to different types of sinks.
4 core api
- *>
- *transforms*
- +stream:base+
- +stream:clojure+
- +stream:fn+
- +stream:future+
- +stream:java+
- +stream:primitive+
- +stream:promise+
- <*>
- add-transforms
- atom-seq
- collect
- collect-collection
- collect-collector
- collect-ifn
- collect-nil
- collect-persistent
- collect-promise
- collect-transient
- collector
- extend-stream
- extend-stream-form
- gen:primitives
- object-seq
- pipe
- pipeline
- pipeline-transform
- primitive-form
- produce
- produce-callable
- produce-deref
- produce-ifn
- produce-nil
- produce-object
- produce-stream
- produce-supplier
- producer
- seqiter
- stream
- to-stream
- unit
*transforms* ^
NONE
(defonce ^:dynamic *transforms*
(atom {:map i/i:map
:map-indexed i/i:map-indexed
:keep i/i:keep
:keep-indexed i/i:keep-indexed
:filter i/i:filter
:remove i/i:remove
:take i/i:take
:take-nth i/i:take-nth
:drop i/i:drop
:drop-last i/i:drop-last
:butlast i/i:butlast
:peek i/i:peek
:prn i/i:prn
:mapcat i/i:mapcat
:delay i/i:delay
:dedupe i/i:dedupe
:partition-all i/i:partition-all
:partition-by i/i:partition-by
:random-sample i/i:random-sample
:sort i/i:sort
:sort-by i/i:sort-by
:reductions i/i:reductions
:some i/i:some
:count i/i:count
:reduce i/i:reduce
:max i/i:max
:min i/i:min
:mean i/i:mean
:stdev i/i:stdev
:last i/i:last
:str i/i:str}))
link
+stream:base+ ^
NONE
(def +stream:base+
'{java.util.stream.Stream
{:produce produce-stream}
java.util.stream.Collector
{:collect collect-collector}})
link
+stream:clojure+ ^
NONE
(def +stream:clojure+
(merge '{clojure.lang.ITransientCollection
{:produce persistent! :collect collect-transient}
clojure.lang.ISeq
{:produce identity}}
(zipmap '[clojure.lang.APersistentMap
clojure.lang.APersistentSet
clojure.lang.APersistentVector
clojure.lang.ASeq
clojure.lang.PersistentList
clojure.lang.PersistentList$EmptyList]
(repeat {:produce identity :collect collect-persistent}))))
link
+stream:fn+ ^
NONE
(def +stream:fn+
'{nil
{:produce produce-nil :collect collect-nil}
Object
{:produce produce-object}
clojure.lang.Fn
{:produce produce-ifn :collect collect-ifn}
Callable
{:produce produce-callable}
Supplier
{:produce produce-supplier}})
link
+stream:future+ ^
NONE
(def +stream:future+
'{Future
{:produce produce-deref}
clojure.lang.IDeref
{:produce produce-deref}})
link
+stream:java+ ^
NONE
(def +stream:java+
'{java.lang.Iterable
{:produce iterator-seq}
java.util.Collection
{:produce seq}
java.util.List
{:produce seq}
java.util.ArrayList
{:produce seq :collect collect-collection}
java.util.LinkedList
{:produce seq :collect collect-collection}})
link
+stream:primitive+ ^
NONE
(def +stream:primitive+
'{"[Z"
{:produce seq :collect collect-booleans}
"[B"
{:produce seq :collect collect-bytes}
"[C"
{:produce seq :collect collect-chars}
"[S"
{:produce seq :collect collect-shorts}
"[I"
{:produce seq :collect collect-ints}
"[J"
{:produce seq :collect collect-longs}
"[F"
{:produce seq :collect collect-floats}
"[D"
{:produce seq :collect collect-doubles}})
link
v 3.0
(defn add-transforms
([key xform & more]
(let [args (apply vector key xform more)]
(apply swap! *transforms* assoc args)
(take-nth 2 args))))
link
(add-transforms :map i/i:map) => '(:map)
v 3.0
(defn atom-seq
([atm f]
(gen/gen (loop []
(let [val (swap! atm f)]
(gen/yield val)
(recur))))))
link
(take 5 (atom-seq (atom -1) inc)) => '(0 1 2 3 4)
v 3.0
(defn collect
([sink supply]
(collect sink identity supply))
([sink xf supply]
(protocol.stream/-collect sink xf supply)))
link
(collect [] (range 5)) => [0 1 2 3 4]
v 3.0
(defn collect-collection
([^Collection sink xf supply]
(reduce (fn [^Collection c e]
(doto c (.add e)))
sink
(xf supply))))
link
(-> (collect-collection (java.util.ArrayList.) (i/i:map inc) (range 5))) => [1 2 3 4 5]
v 3.0
(defn collect-collector
([^Collector sink xf supply]
(-> (ISeqSpliterator/stream (seqiter xf supply))
(.collect sink))))
link
(collect-collector (Collectors/toList) (i/i:map inc) (range 5)) => [1 2 3 4 5]
v 3.0
(defn collect-ifn
([^clojure.lang.Fn f xf supply]
(let [s (seqiter xf supply)]
(f s))))
link
(-> (collect-ifn int-array (i/i:map inc) (range 5)) seq) => '(1 2 3 4 5)
v 3.0
(defn collect-nil
([_ xf supply]
(seqiter xf supply)))
link
(collect-nil nil (i/i:map inc) (range 5)) => seq?
v 4.1
(defn collect-persistent
[sink xf supply]
(into sink (xf supply)))
link
(collect-persistent [] (i/i:map inc) (range 5)) => [1 2 3 4 5] (collect-persistent #{} (i/i:map inc) (range 5)) => #{1 2 3 4 5}
v 3.0
(defn collect-promise
([sink xf supply]
(deliver sink (first (seqiter xf supply)))))
link
@(doto (promise) (collect (i/i:map inc) (range 5))) => 1
v 3.0
(defn collect-transient
([^ITransientCollection sink xf supply]
(reduce conj! sink (xf supply))))
link
(-> (collect-transient (transient []) (i/i:map inc) (range 5)) (persistent!)) => [1 2 3 4 5]
v 3.0
(defn collector
([stages sink]
(let [xforms (pipeline stages)]
(fn
([source]
(collect sink xforms (produce source)))
([stages source]
(collect sink
(comp (pipeline stages)
xforms)
(produce source)))))))
link
(collector [[:map inc]] []) => fn? (pipe (producer (range 5) [[:map inc]]) [] (collector [[:map inc]] [])) => [2 3 4 5 6] ((collector [[:map inc]] []) (producer (range 5) [[:map inc]])) => [2 3 4 5 6] ((collector [[:map inc]] []) [[:map inc]] (producer (range 5) [[:map inc]])) => [3 4 5 6 7]
v 3.0
(defmacro extend-stream
([all]
(mapv extend-stream-form (cond (symbol? all)
@(resolve all)
:else all))))
link
(resolve 'extend-stream) => var?
v 3.0
(defn extend-stream-form
([[type {:keys [produce collect]}]]
(let [type (if (string? type)
`(Class/forName ~type)
type)]
`(do (extend-type ~type
~@(if produce
`[std.protocol.stream/ISource
(-produce [~'source] (~produce ~'source))])
~@(if collect
`[std.protocol.stream/ISink
(-collect [~'sink ~'xf ~'supply] (~collect ~'sink ~'xf ~'supply))]))
~type))))
link
(extend-stream-form [nil '{:produce 'produce-nil :collect 'collect-nil}]) => any?
v 3.0
(defmacro gen:primitives
([]
(mapv primitive-form +stream:primitive+)))
link
(produce (byte-array 5)) => '(0 0 0 0 0)
v 3.0
(defn object-seq
([obj f]
(gen/gen (loop []
(let [e (f obj)]
(gen/yield e)
(recur))))))
link
(take 5 (object-seq (volatile! -1) #(vswap! % inc))) => '(0 1 2 3 4)
v 3.0
(defn pipe
([source stages sink]
(let [xforms (pipeline stages)
supply (produce source)]
(collect sink xforms supply))))
link
(pipe (range 5) [[:map inc] [:map inc]] []) => [2 3 4 5 6]
v 3.0
(defn pipeline
([stages]
(apply comp (reverse (pipeline-transform stages)))))
link
(pipeline [[:map inc] [:map inc]]) => fn?
v 3.0
(defn pipeline-transform
([stages]
(map (fn [xf]
(cond (vector? xf)
(let [[k & args] xf
f (or (get @*transforms* k)
(throw (ex-info "Invalid key" {:key k})))]
(apply f args))
:else xf))
stages)))
link
(pipeline-transform [[:map inc] [:map inc]]) => (contains [fn? fn?])
v 3.0
(defn primitive-form
([[class {:keys [collect]}]]
(let [fn-sym collect
sym-name (name collect)
sym-name (subs sym-name (count "collect-") (dec (count sym-name)))
aset-sym (symbol (str "aset-" sym-name))]
`(defn ~fn-sym
([~'sink ~'xf ~'supply]
(let [len# (count ~'sink)
xs# (sequence ~'xf ~'supply)]
(loop [i# 0
x# (first xs#)
xs# (rest xs#)]
(if-not x#
~'sink
(do (~aset-sym ~'sink i# x#)
(recur (inc i#) (first xs#) (rest xs#)))))))))))
link
(primitive-form '["[Z" {:produce seq :collect collect-booleans}])
v 3.0
(defn produce-callable
([^Callable f]
(produce (.call f))))
link
(produce-callable (fn [] 1)) => '(1)
v 3.0
(defn produce-deref
([source]
(produce (deref source))))
link
(produce-deref (volatile! 1)) => '(1)
v 3.0
(defn produce-ifn
([^clojure.lang.IFn f]
(produce (.invoke f))))
link
(produce-ifn (fn [] 1)) => '(1) (produce-ifn (fn [] (range 5))) => '(0 1 2 3 4)
v 3.0
(defn produce-stream
([^Stream source]
(-> source .iterator iterator-seq)))
link
(->> (to-stream (range 5)) (produce-stream) (map inc)) => '(1 2 3 4 5)
v 3.0
(defn produce-supplier
([^Supplier f]
(.get f)))
link
(produce-supplier (fn/fn:supplier [] 1)) => 1
v 3.0
(defn producer
([source stages]
(let [xforms (pipeline stages)]
(seqiter xforms (produce source)))))
link
(producer (range 5) [[:map inc]]) => seq?
seqiter ^
[coll] [xform coll]
creates an non-chunking iterator from a transducer (sink)
v 3.0
(defn seqiter
([coll] coll)
([xform coll]
(xform coll)))
link
(seqiter (i/i:map inc) (range 5)) => (range 1 6)
v 3.0
(defn stream
([source] source)
([source & args]
(let [[stages sink] [(butlast args) (last args)]
source (if (= :<*> source) :stream/<*> source)
sink (if (= :<*> sink) :stream/<*> sink)]
(cond (and (= :stream/<*> source)
(= :stream/<*> sink))
(pipeline stages)
(= :stream/<*> sink)
(producer source stages)
(= :stream/<*> source)
(collector stages sink)
:else
(pipe source stages sink)))))
link
;; pipe action (stream (range 5) [:map inc] [:map inc] []) => [2 3 4 5 6] ;; producer (stream (range 5) [:map inc] [:map inc] (<*>)) => seq? ;; collector (stream (<*>) [:map inc] [:map inc]) => fn? ;; stage (stream (<*>) [:map inc] [:map inc] (<*>)) => fn?
5 std.lib.stream: A Comprehensive Summary
The std.lib.stream module provides a powerful and flexible framework for processing sequences of data using transducers. It abstracts over various data sources (producers) and sinks (collectors), allowing for the construction of complex data pipelines with a unified API. The module extends Clojure's transducer capabilities with a rich set of custom transducers (xform namespace) and provides seamless integration with Java Streams and other collection types.
The module is organized into two main sub-namespaces:
5.1 std.lib.stream.xform
This namespace provides a comprehensive collection of transducers (transforming functions) that can be used in data processing pipelines. These transducers extend and complement Clojure's built-in transducers, offering functionalities for mapping, filtering, reducing, statistical analysis, and windowing.
x:map [& [f]]: A transducer that applies a functionfto each element.nx:map-indexed [f]: A transducer that applies a functionfto[index element]pairs.nx:filter [& [pred]]: A transducer that retains elements for whichpredreturns true.nx:remove [& [f]]: A transducer that removes elements for whichfreturns true.nx:keep [& [f]]: A transducer that retains non-nil results of applyingfto each element.nx:keep-indexed [f]: A transducer that retains non-nil results of applyingfto[index element]pairs.nx:prn [& [f]]: A transducer that prints each element (or(f element)) as a side effect.nx:peek [& [f]]: A transducer that appliesfto each element as a side effect, returning the original element.nx:delay [ms]: A transducer that introduces a delay (in milliseconds) after processing each element.nx:mapcat [& [f]]: A transducer that maps a functionfover elements and concatenates the results.nx:pass [& [init]]: An identity transducer.nx:apply [rf]: A transducer that applies a reduction functionrfto the accumulated result.nx:reduce [f & [init]]: A transducer that accumulates results using a reduction functionf.nx:take [& [n]]: A transducer that takes up tonelements.nx:take-last [n]: A transducer that takes the lastnelements.nx:drop [& [n]]: A transducer that dropsnelements from the beginning.nx:drop-last [n]: A transducer that drops the lastnelements.nx:butlast []: A transducer that drops the last element.nx:some [& [f]]: A transducer that returns the first non-nil result of applyingfto elements, ornil.nx:last []: A transducer that returns the last element.nx:count []: A transducer that counts the number of elements.nx:min [& [comparator]]: A transducer that finds the minimum element.nx:max [& [comparator]]: A transducer that finds the maximum element.nx:mean []: A transducer that calculates the mean of numeric elements.nx:stdev []: A transducer that calculates the standard deviation of numeric elements.nx:str []: A transducer that concatenates elements into a single string.nx:sort [& [cmp]]: A transducer that sorts all elements.nx:sort-by [xf & [cmp]]: A transducer that sorts elements based on a key functionxf.nx:reductions [f & [init]]: A transducer that produces a sequence of intermediate reduction results.nx:wrap [open close]: A transducer that wraps the sequence withopenandcloseelements.nx:time [tag-or-f xform]: A transducer that measures the time taken to process elements, printing the result.n*x:window [n & [f invf]]: A transducer that produces a sliding window ofnelements.
5.2 std.lib.stream (Main Namespace)
This namespace provides the core API for constructing and executing data pipelines using transducers, integrating various data sources and sinks.
produce [obj]: A generic function (multimethod viaprotocol.stream/-produce) that converts an object into a sequence (producer).ncollect [sink xf supply]: A generic function (multimethod viaprotocol.stream/-collect) that consumes a sequencesupply(optionally transformed byxf) into asink(collector).nseqiter [& [xform] coll & colls]: Creates a non-chunking iterator from a transducer and a collection, useful for fine-grained control over iteration.nunit [xform input]: Applies a transducerxformto a singleinput.nextend-stream-form [type-map]: Helper forextend-streammacro.nextend-stream [all]: A macro to extendstd.protocol.stream/ISourceandstd.protocol.stream/ISinkto various types based on a map of type-to-produce/collect functions.n*transforms*: An atom holding a map of all available transducers (fromstd.lib.stream.xform) keyed by their symbolic names.nadd-transforms [key xform & more]: Adds custom transducers to*transforms*.npipeline-transform [stages]: Converts a list of stage definitions (e.g.,[:map inc]) into a list of actual transducer functions.npipeline [stages]: Creates a composite transducer (pipeline) from a list of stage definitions.npipe [source stages sink]: The core function for executing a stream. It takes asource, a list ofstages(transducers), and asink, piping data from source through stages to sink.nproducer [source stages]: Creates a sequence producer from asourceand a list ofstages.ncollector [stages sink]: Creates a collection function (sink) from a list ofstagesand asink.n<*>: A keyword (:stream/<*>) used as a placeholder to denote a missing source or sink instreamoperations.nstream [source & args]: A versatile function that constructs and executes stream operations. It can act as apipeline,producer,collector, orpipedepending on whether<*>is used for source or sink.n*> [& args]: A shortcut macro forstream.n Producer/Collector Implementations:nproduce-nil,produce-object,produce-ifn,produce-callable,produce-supplier: Functions to produce sequences fromnil, generic objects, functions,Callables, andSuppliers.ncollect-nil,collect-ifn: Functions to collect sequences intonilor by applying a function.n+stream:fn+: A map defining produce/collect implementations for various function-like types.n+stream:primitive+: A map defining produce/collect implementations for Java primitive arrays.nprimitive-form [type-map]: Helper forgen:primitives.ngen:primitives []: A macro to generate produce/collect functions for primitive arrays.ncollect-transient,collect-collection: Functions to collect sequences intoITransientCollections andjava.util.Collections.n+stream:java+,+stream:clojure+: Maps defining produce/collect implementations for various Java and Clojure collection types.nto-stream [arr]: Converts a Clojure sequence to ajava.util.stream.Stream.nproduce-stream [source]: Produces a sequence from ajava.util.stream.Stream.ncollect-collector [sink xf supply]: Collects a sequence into ajava.util.stream.Collector.n+stream:base+: Map defining produce/collect implementations for Java Streams and Collectors.nproduce-deref [source]: Produces a sequence by dereferencing a source.ncollect-promise [sink xf supply]: Collects a sequence into aPromise.n+stream:promise+,+stream:future+: Maps defining produce/collect implementations forPromises andFutures.n Sources:natom-seq [atm f]: Constructs a lazy sequence by repeatedly updating and yielding values from an atom.nobject-seq [obj f]: Constructs a lazy sequence by repeatedly applying a functionfto an object and yielding the result.
Overall Importance:
The std.lib.stream module is a fundamental component for data processing and transformation within the foundation-base project. Its key contributions include:
- Unified Data Processing API: Provides a consistent and flexible way to define and execute data pipelines, regardless of the underlying data source or sink.n Transducer-Based Efficiency: Leverages Clojure's transducers for efficient, composable, and lazy data transformations, minimizing intermediate allocations.n Extensibility: The protocol-driven design allows for easy integration of new data sources, sinks, and custom transducers.n Interoperability: Seamlessly integrates with various Java and Clojure collection types, as well as Java Streams,
Callable,Supplier,Future, andPromise.n Rich Set of Transformations: Thexformnamespace provides a comprehensive library of transducers for common data manipulation, aggregation, and statistical analysis tasks.n* Declarative Pipeline Construction: Thepipelineandstreamfunctions allow for declarative definition of data processing workflows, improving readability and maintainability.
By offering these powerful streaming and data transformation capabilities, std.lib.stream significantly enhances the foundation-base project's ability to handle and process diverse data efficiently and flexibly, which is vital for its multi-language development ecosystem.