@zio.dev/zio-blocks 0.0.33 → 0.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guides/compile-time-resource-safety-with-scope.md +16 -17
- package/guides/getting-started-with-mux.md +1507 -0
- package/guides/query-dsl-extending.md +161 -102
- package/guides/query-dsl-fluent-builder.md +217 -157
- package/guides/query-dsl-reified-optics.md +12 -10
- package/guides/query-dsl-sql.md +246 -165
- package/guides/telemetry-guide.md +1069 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +292 -50
- package/package.json +1 -1
- package/plans/config-follow-up-prs.md +188 -0
- package/plans/config-pr-assessment-roadmap.md +310 -0
- package/reference/MuxDataFlow.jsx +250 -0
- package/reference/async.md +651 -0
- package/reference/chunk.md +3533 -308
- package/reference/codegen/case-class.md +436 -0
- package/reference/codegen/emitter-config.md +383 -0
- package/reference/codegen/examples.md +664 -0
- package/reference/codegen/field.md +316 -0
- package/reference/codegen/index.md +317 -0
- package/reference/codegen/scala-emitter.md +392 -0
- package/reference/codegen/scala-file.md +276 -0
- package/reference/codegen/sealed-trait.md +408 -0
- package/reference/codegen/type-definition.md +340 -0
- package/reference/codegen/type-ref.md +201 -0
- package/reference/combinators.md +347 -117
- package/reference/config.md +158 -0
- package/reference/context.md +4 -4
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +825 -0
- package/reference/endpoint/path-codec.md +237 -0
- package/reference/endpoint/route-pattern.md +196 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +212 -0
- package/reference/html.md +1120 -0
- package/reference/htmx/attribute-values.md +359 -0
- package/reference/htmx/hx-encoding.md +111 -0
- package/reference/htmx/hx-params.md +204 -0
- package/reference/htmx/hx-swap.md +276 -0
- package/reference/htmx/hx-sync.md +251 -0
- package/reference/htmx/hx-target.md +314 -0
- package/reference/htmx/hx-trigger.md +457 -0
- package/reference/htmx/hx-url-update.md +239 -0
- package/reference/htmx/index.md +855 -0
- package/reference/http-model/index.md +47 -0
- package/reference/http-model/model.md +1481 -0
- package/reference/http-model/schema.md +747 -0
- package/reference/maybe.md +826 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +823 -0
- package/reference/openapi.md +1351 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -2
- package/reference/resource-management/scope.md +28 -12
- package/reference/resource-management/wire.md +3 -7
- package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
- package/reference/ringbuffer/MpscDiagram.jsx +618 -0
- package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
- package/reference/ringbuffer/SpscDiagram.jsx +677 -0
- package/reference/ringbuffer/advanced.mdx +109 -0
- package/reference/ringbuffer/index.mdx +145 -0
- package/reference/ringbuffer/mpmc.mdx +151 -0
- package/reference/ringbuffer/mpsc.mdx +132 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +344 -0
- package/reference/{allows.md → schema/allows.md} +4 -4
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +2 -3
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +480 -0
- package/reference/schema/built-in-codecs/csv.md +564 -0
- package/reference/schema/built-in-codecs/index.md +77 -0
- package/reference/schema/built-in-codecs/json/index.md +295 -0
- package/reference/schema/built-in-codecs/json/json-config.md +217 -0
- package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
- package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
- package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
- package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
- package/reference/schema/built-in-codecs/messagepack.md +508 -0
- package/reference/schema/built-in-codecs/thrift.md +433 -0
- package/reference/schema/built-in-codecs/toon.md +1078 -0
- package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
- package/reference/schema/built-in-codecs/yaml.md +552 -0
- package/reference/{codec.md → schema/codec.md} +10 -10
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +50 -0
- package/reference/schema/migration.md +297 -0
- package/reference/{modifier.md → schema/modifier.md} +58 -7
- package/reference/{optics.md → schema/optics.md} +2 -2
- package/reference/{patch.md → schema/patch.md} +1 -1
- package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
- package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
- package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
- package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
- package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
- package/reference/{schema.md → schema/schema.md} +12 -0
- package/reference/{structural-types.md → schema/structural-types.md} +1 -1
- package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
- package/reference/smithy.md +533 -0
- package/reference/sql/db-codec-deriver.md +71 -0
- package/reference/sql/db-codec.md +687 -0
- package/reference/sql/db-con.md +271 -0
- package/reference/sql/db-connection.md +153 -0
- package/reference/sql/db-param-writer.md +77 -0
- package/reference/sql/db-param.md +66 -0
- package/reference/sql/db-result-reader.md +146 -0
- package/reference/sql/db-tx.md +82 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +254 -0
- package/reference/sql/index.md +341 -0
- package/reference/sql/repo.md +600 -0
- package/reference/sql/sql-dialect.md +73 -0
- package/reference/sql/sql-logger.md +62 -0
- package/reference/sql/sql-name-mapper.md +70 -0
- package/reference/sql/table-metadata.md +134 -0
- package/reference/sql/table.md +448 -0
- package/reference/sql/transactor-zio.md +399 -0
- package/reference/sql/transactor.md +353 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/concurrent-operators.md +106 -0
- package/reference/streams/index.md +653 -0
- package/reference/streams/pipeline.md +718 -0
- package/reference/streams/reader.md +1284 -0
- package/reference/streams/scala-2-compatibility.md +55 -0
- package/reference/streams/sink.md +1426 -0
- package/reference/streams/stream.md +2526 -0
- package/reference/streams/writer.md +1045 -0
- package/reference/streams/zero-boxing.md +275 -0
- package/reference/telemetry.md +693 -0
- package/reference/typeid.md +5 -19
- package/sidebars.js +238 -43
- package/reference/formats.md +0 -694
- package/reference/http-model.md +0 -1716
- package/reference/streams.md +0 -989
- package/ringbuffer.md +0 -249
- /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
- /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
- /package/reference/{lazy.md → schema/lazy.md} +0 -0
- /package/reference/{reflect.md → schema/reflect.md} +0 -0
- /package/reference/{registers.md → schema/registers.md} +0 -0
- /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
- /package/reference/{syntax.md → schema/syntax.md} +0 -0
- /package/reference/{validation.md → schema/validation.md} +0 -0
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: pipeline
|
|
3
|
+
title: "Pipeline"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`Pipeline[-In, +Out]` is a **reusable, composable stream transformation** that converts elements of type `In` into elements of type `Out`. Pipelines are first-class values: you can define them once, compose them with `andThen`, and apply them to any [Stream](./stream.md) via `stream.via(pipe)` or to any `Sink` via `pipe.andThenSink(sink)`.
|
|
7
|
+
|
|
8
|
+
`Pipeline`:
|
|
9
|
+
- Is contravariant in `In` and covariant in `Out` (like a function `In => Out`)
|
|
10
|
+
- Can be applied to a **Stream** (transforming the output) or a **Sink** (pre-processing the input)
|
|
11
|
+
- Participates in JVM primitive specialization to avoid boxing
|
|
12
|
+
|
|
13
|
+
Here is the structural shape of the `Pipeline` type:
|
|
14
|
+
|
|
15
|
+
```scala
|
|
16
|
+
abstract class Pipeline[-In, +Out] {
|
|
17
|
+
def andThen[C](that: Pipeline[Out, C]): Pipeline[In, C]
|
|
18
|
+
def applyToStream[E](stream: Stream[E, In]): Stream[E, Out]
|
|
19
|
+
def applyToSink[E, Z](sink: Sink[E, Out, Z]): Sink[E, In, Z]
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
Pipelines solve the problem of reusing stream transformations across different streams and sinks. Without pipelines, you repeat filtering and mapping logic for every stream. With pipelines, you define transformations once as first-class values, compose them freely, and apply them anywhere.
|
|
26
|
+
|
|
27
|
+
### The Problem
|
|
28
|
+
|
|
29
|
+
When you build stream processing logic, you often write chains like:
|
|
30
|
+
|
|
31
|
+
```scala
|
|
32
|
+
stream
|
|
33
|
+
.filter(_ > 0)
|
|
34
|
+
.map(_ * 2)
|
|
35
|
+
.take(100)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This works, but the transformation is tied to a specific stream. If you want to apply the same logic to a different stream, or to a sink instead, you have to repeat yourself. You cannot pass the chain around as a value, store it in a variable, or compose it with other transformations.
|
|
39
|
+
|
|
40
|
+
### The Solution
|
|
41
|
+
|
|
42
|
+
`Pipeline[-In, +Out]` lifts stream transformations into first-class values. You define a pipeline once, compose it with other pipelines using `andThen`, and apply it wherever you need:
|
|
43
|
+
|
|
44
|
+
```scala
|
|
45
|
+
import zio.blocks.streams.*
|
|
46
|
+
|
|
47
|
+
// Define once
|
|
48
|
+
val normalize: Pipeline[Int, Int] =
|
|
49
|
+
Pipeline.filter[Int](_ > 0)
|
|
50
|
+
.andThen(Pipeline.map[Int, Int](_ * 2))
|
|
51
|
+
.andThen(Pipeline.take(100))
|
|
52
|
+
|
|
53
|
+
// Apply to any stream
|
|
54
|
+
val stream1 = Stream(1, 2, 3, 4, 5)
|
|
55
|
+
val stream2 = Stream(10, 20, 30, 40, 50)
|
|
56
|
+
val stream3 = Stream(-5, 3, 7, 2, 8, 1, 9)
|
|
57
|
+
|
|
58
|
+
val result1 = stream1.via(normalize).runCollect
|
|
59
|
+
val result2 = stream2.via(normalize).runCollect
|
|
60
|
+
|
|
61
|
+
// Apply to a sink (pre-process input before the sink sees it)
|
|
62
|
+
val normalizedSink = normalize.andThenSink(Sink.collectAll[Int])
|
|
63
|
+
val result3 = stream3.run(normalizedSink)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`Pipeline` forms a **category** in the mathematical sense:
|
|
67
|
+
|
|
68
|
+
| Law | Statement |
|
|
69
|
+
|----------------|------------------------------------------------------|
|
|
70
|
+
| Left identity | `Pipeline.identity andThen p == p` |
|
|
71
|
+
| Right identity | `p andThen Pipeline.identity == p` |
|
|
72
|
+
| Associativity | `(p andThen q) andThen r == p andThen (q andThen r)` |
|
|
73
|
+
|
|
74
|
+
These laws guarantee that pipelines compose predictably, regardless of how you parenthesize.
|
|
75
|
+
|
|
76
|
+
### Architecture
|
|
77
|
+
|
|
78
|
+
`Pipeline` sits between `Stream` and `Sink`, mediating how elements flow:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Applying to a Stream (via):
|
|
82
|
+
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
|
|
83
|
+
│ Stream[E, In]│ ──→ │ Pipeline[In, Out]│ ──→ │Stream[E, Out]│
|
|
84
|
+
└──────────────┘ └──────────────────┘ └──────────────┘
|
|
85
|
+
|
|
86
|
+
Applying to a Sink (andThenSink):
|
|
87
|
+
┌──────────────────┐ ┌────────────────┐ ┌──────────────┐
|
|
88
|
+
│ Pipeline[In, Out]│ ──→ │ Sink[E, Out, Z]│ ──→ │Sink[E, In, Z]│
|
|
89
|
+
└──────────────────┘ └────────────────┘ └──────────────┘
|
|
90
|
+
|
|
91
|
+
Composing two Pipelines (andThen):
|
|
92
|
+
┌──────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
93
|
+
│ Pipeline[A, B] │ ──→ │ Pipeline[B, C] │ ──→ │ Pipeline[A, C] │
|
|
94
|
+
└──────────────────┘ └──────────────────┘ └─────────────────┘
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Construction
|
|
98
|
+
|
|
99
|
+
Pipelines are built using factory methods on the `Pipeline` companion object. Each factory creates a pipeline that performs a specific transformation: mapping elements, filtering, collecting, or controlling flow. All factories support JVM primitive specialization through implicit `JvmType.Infer` parameters.
|
|
100
|
+
|
|
101
|
+
### `Pipeline.map[A, B]` — Transform Each Element
|
|
102
|
+
|
|
103
|
+
Applies a function to every element, producing a new element type. Here is the signature:
|
|
104
|
+
|
|
105
|
+
```scala
|
|
106
|
+
object Pipeline {
|
|
107
|
+
def map[A, B](f: A => B)(implicit jtA: JvmType.Infer[A], jtB: JvmType.Infer[B]): Pipeline[A, B]
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This is the most common pipeline constructor:
|
|
112
|
+
|
|
113
|
+
```scala
|
|
114
|
+
import zio.blocks.streams.*
|
|
115
|
+
|
|
116
|
+
val doubler = Pipeline.map[Int, Int](_ * 2)
|
|
117
|
+
// doubler: Pipeline[Int, Int] = zio.blocks.streams.Pipeline$MapPipeline@5751527e
|
|
118
|
+
val toStr = Pipeline.map[Int, String](_.toString)
|
|
119
|
+
// toStr: Pipeline[Int, String] = zio.blocks.streams.Pipeline$MapPipeline@21969325
|
|
120
|
+
|
|
121
|
+
val result = Stream(1, 2, 3).via(doubler).runCollect
|
|
122
|
+
// result: Either[Nothing, Chunk[Int]] = Right(IndexedSeq(2, 4, 6))
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `Pipeline.filter[A]` — Keep Matching Elements
|
|
126
|
+
|
|
127
|
+
Keeps only elements that satisfy a predicate. Here is the signature:
|
|
128
|
+
|
|
129
|
+
```scala
|
|
130
|
+
object Pipeline {
|
|
131
|
+
def filter[A](pred: A => Boolean)(implicit jtA: JvmType.Infer[A]): Pipeline[A, A]
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Note that the output type is the same as the input type — filtering does not change the element type:
|
|
136
|
+
|
|
137
|
+
```scala
|
|
138
|
+
import zio.blocks.streams.*
|
|
139
|
+
|
|
140
|
+
val positives = Pipeline.filter[Int](_ > 0)
|
|
141
|
+
// positives: Pipeline[Int, Int] = zio.blocks.streams.Pipeline$FilterPipeline@2148b90d
|
|
142
|
+
|
|
143
|
+
val result = Stream(-2, -1, 0, 1, 2).via(positives).runCollect
|
|
144
|
+
// result: Either[Nothing, Chunk[Int]] = Right(IndexedSeq(1, 2))
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `Pipeline.collect[A, B]` — Partial Function Transformation
|
|
148
|
+
|
|
149
|
+
Applies a partial function: only elements for which the function is defined pass through, and they are transformed to the output type. This combines filtering and mapping in one step. Here is the signature:
|
|
150
|
+
|
|
151
|
+
```scala
|
|
152
|
+
object Pipeline {
|
|
153
|
+
def collect[A, B](pf: PartialFunction[A, B])(implicit jtA: JvmType.Infer[A], jtB: JvmType.Infer[B]): Pipeline[A, B]
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Use `collect` when you need to filter and transform simultaneously:
|
|
158
|
+
|
|
159
|
+
```scala
|
|
160
|
+
import zio.blocks.streams.*
|
|
161
|
+
|
|
162
|
+
val extractInts = Pipeline.collect[Any, Int] { case n: Int => n }
|
|
163
|
+
// extractInts: Pipeline[Any, Int] = zio.blocks.streams.Pipeline$CollectPipeline@63a54682
|
|
164
|
+
|
|
165
|
+
val result = Stream(1, "a", 2, "b", 3).via(extractInts).runCollect
|
|
166
|
+
// result: Either[Nothing, Chunk[Int]] = Right(IndexedSeq(1, 2, 3))
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### `Pipeline.take[A]` — First N Elements
|
|
170
|
+
|
|
171
|
+
Passes through at most the first `n` elements, then stops. Here is the signature:
|
|
172
|
+
|
|
173
|
+
```scala
|
|
174
|
+
object Pipeline {
|
|
175
|
+
def take[A](n: Long): Pipeline[A, A]
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
This naturally short-circuits — upstream stops producing once `n` elements have passed:
|
|
180
|
+
|
|
181
|
+
```scala
|
|
182
|
+
import zio.blocks.streams.*
|
|
183
|
+
|
|
184
|
+
val firstFive = Pipeline.take[Int](5)
|
|
185
|
+
// firstFive: Pipeline[Int, Int] = zio.blocks.streams.Pipeline$TakePipeline@53108fba
|
|
186
|
+
|
|
187
|
+
val result = Stream.range(0, 1000).via(firstFive).runCollect
|
|
188
|
+
// result: Either[Nothing, Chunk[Int]] = Right(IndexedSeq(0, 1, 2, 3, 4))
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### `Pipeline.drop[A]` — Skip First N Elements
|
|
192
|
+
|
|
193
|
+
Skips the first `n` elements, then passes through the rest. Here is the signature:
|
|
194
|
+
|
|
195
|
+
```scala
|
|
196
|
+
object Pipeline {
|
|
197
|
+
def drop[A](n: Long): Pipeline[A, A]
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Use `drop` to skip headers, metadata, or warm-up elements:
|
|
202
|
+
|
|
203
|
+
```scala
|
|
204
|
+
import zio.blocks.streams.*
|
|
205
|
+
|
|
206
|
+
val skipHeader = Pipeline.drop[String](1)
|
|
207
|
+
// skipHeader: Pipeline[String, String] = zio.blocks.streams.Pipeline$DropPipeline@5e27a41e
|
|
208
|
+
|
|
209
|
+
val result = Stream("header", "row1", "row2").via(skipHeader).runCollect
|
|
210
|
+
// result: Either[Nothing, Chunk[String]] = Right(IndexedSeq("row1", "row2"))
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### `Pipeline.identity[A]` — Pass-Through
|
|
214
|
+
|
|
215
|
+
The identity pipeline that passes all elements through unchanged. This is the neutral element for `andThen` composition. Here is the signature:
|
|
216
|
+
|
|
217
|
+
```scala
|
|
218
|
+
object Pipeline {
|
|
219
|
+
def identity[A](implicit jtA: JvmType.Infer[A]): Pipeline[A, A]
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
You rarely construct `identity` explicitly, but it is important as a base case in generic pipeline-building code:
|
|
224
|
+
|
|
225
|
+
```scala
|
|
226
|
+
import zio.blocks.streams.*
|
|
227
|
+
|
|
228
|
+
val noOp = Pipeline.identity[Int]
|
|
229
|
+
// noOp: Pipeline[Int, Int] = zio.blocks.streams.Pipeline$MapPipeline@52ba998
|
|
230
|
+
|
|
231
|
+
// These are equivalent:
|
|
232
|
+
// stream.via(noOp) == stream
|
|
233
|
+
// noOp.andThen(p) == p
|
|
234
|
+
// p.andThen(noOp) == p
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Composing Pipelines
|
|
238
|
+
|
|
239
|
+
Pipelines compose into larger, more complex transformations using `andThen`. Because `Pipeline` forms a mathematical category, composition is associative and respects identity, so you can build pipelines incrementally or conditionally without worrying about how you parenthesize or combine them.
|
|
240
|
+
|
|
241
|
+
### `Pipeline#andThen[C]` — Sequential Composition
|
|
242
|
+
|
|
243
|
+
Composes two pipelines into one, applying `this` first and `that` second. Here is the signature:
|
|
244
|
+
|
|
245
|
+
```scala
|
|
246
|
+
trait Pipeline[-In, +Out] {
|
|
247
|
+
def andThen[C](that: Pipeline[Out, C]): Pipeline[In, C]
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`andThen` is the key operation that makes pipelines composable. Because `Pipeline` forms a category, composition is associative — you can group `andThen` calls however you like and get the same result:
|
|
252
|
+
|
|
253
|
+
```scala
|
|
254
|
+
import zio.blocks.streams.*
|
|
255
|
+
|
|
256
|
+
// Individual steps
|
|
257
|
+
val filterPositive = Pipeline.filter[Int](_ > 0)
|
|
258
|
+
val double = Pipeline.map[Int, Int](_ * 2)
|
|
259
|
+
val takeFirst10 = Pipeline.take[Int](10)
|
|
260
|
+
|
|
261
|
+
// Compose into a single reusable pipeline
|
|
262
|
+
val normalize = filterPositive
|
|
263
|
+
.andThen(double)
|
|
264
|
+
.andThen(takeFirst10)
|
|
265
|
+
|
|
266
|
+
// Apply to any stream
|
|
267
|
+
val result = Stream(-5, 3, -1, 7, 2, 0, 9, 4, 8, 6, 1, 10)
|
|
268
|
+
.via(normalize)
|
|
269
|
+
.runCollect
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Building Pipelines Conditionally
|
|
273
|
+
|
|
274
|
+
Because pipelines are values, you can build them dynamically:
|
|
275
|
+
|
|
276
|
+
```scala
|
|
277
|
+
import zio.blocks.streams.*
|
|
278
|
+
|
|
279
|
+
def buildPipeline(limit: Option[Int], onlyPositive: Boolean): Pipeline[Int, Int] = {
|
|
280
|
+
val base = if (onlyPositive) Pipeline.identity[Int].andThen(Pipeline.filter(_ > 0)) else Pipeline.identity[Int]
|
|
281
|
+
limit.fold(base)(n => base.andThen(Pipeline.take(n.toLong)))
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Applying to a Stream
|
|
286
|
+
|
|
287
|
+
Apply a pipeline to a stream using `via` to transform its output elements. This is the most direct way to use a pipeline: define it once and apply it to multiple streams without repeating the transformation logic.
|
|
288
|
+
|
|
289
|
+
### `Stream#via[B]` — Apply a Pipeline to a Stream
|
|
290
|
+
|
|
291
|
+
The primary way to use a pipeline is through `Stream.via`. Here is the signature:
|
|
292
|
+
|
|
293
|
+
```scala
|
|
294
|
+
trait Stream[+E, +A] {
|
|
295
|
+
def via[B](pipe: Pipeline[A, B]): Stream[E, B]
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Under the hood, `via` calls `pipe.applyToStream(this)`. Each pipeline type delegates to a specific `Stream` node — for example, `Pipeline.map` creates a `Stream.Mapped`, and `Pipeline.filter` creates a `Stream.Filtered`.
|
|
300
|
+
|
|
301
|
+
The key advantage of `via` over inline methods is **reuse**: define the pipeline once and apply it to multiple streams:
|
|
302
|
+
|
|
303
|
+
```scala
|
|
304
|
+
import zio.blocks.streams.*
|
|
305
|
+
|
|
306
|
+
// A reusable cleaning pipeline for sensor data
|
|
307
|
+
val cleanSensorData: Pipeline[Double, Double] =
|
|
308
|
+
Pipeline.filter[Double](d => !d.isNaN && !d.isInfinite)
|
|
309
|
+
.andThen(Pipeline.filter(d => d >= -100.0 && d <= 100.0))
|
|
310
|
+
// cleanSensorData: Pipeline[Double, Double] = zio.blocks.streams.Pipeline$Composed@1eea4c07
|
|
311
|
+
|
|
312
|
+
// Apply to different sensor streams
|
|
313
|
+
val sensorStream1 = Stream(45.5, 67.2, Double.NaN, 23.1)
|
|
314
|
+
// sensorStream1: Stream[Nothing, Double] = Stream(45.5, 67.2, NaN, 23.1)
|
|
315
|
+
val sensorStream2 = Stream(89.9, -200.0, 12.5, 55.0)
|
|
316
|
+
// sensorStream2: Stream[Nothing, Double] = Stream(89.9, -200.0, 12.5, 55.0)
|
|
317
|
+
|
|
318
|
+
val sensor1Result = sensorStream1.via(cleanSensorData).runCollect
|
|
319
|
+
// sensor1Result: Either[Nothing, Chunk[Double]] = Right(
|
|
320
|
+
// IndexedSeq(45.5, 67.2, 23.1)
|
|
321
|
+
// )
|
|
322
|
+
val sensor2Result = sensorStream2.via(cleanSensorData).runCollect
|
|
323
|
+
// sensor2Result: Either[Nothing, Chunk[Double]] = Right(
|
|
324
|
+
// IndexedSeq(89.9, 12.5, 55.0)
|
|
325
|
+
// )
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### `Pipeline#applyToStream[E]` — Direct Application
|
|
329
|
+
|
|
330
|
+
You can also call `applyToStream` directly. This is equivalent to `via` but reads left-to-right from the pipeline's perspective. Here is the signature:
|
|
331
|
+
|
|
332
|
+
```scala
|
|
333
|
+
trait Pipeline[-In, +Out] {
|
|
334
|
+
def applyToStream[E](stream: Stream[E, In]): Stream[E, Out]
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
`stream.via(pipe)` and `pipe.applyToStream(stream)` are identical in behavior. Prefer `via` for readability in stream chains.
|
|
339
|
+
|
|
340
|
+
## Applying to a Sink
|
|
341
|
+
|
|
342
|
+
Apply a pipeline to a sink using `andThenSink` to pre-process the sink's input elements. This is the dual of `via`: instead of transforming a stream's output, you transform what the sink receives before it processes it.
|
|
343
|
+
|
|
344
|
+
### `Pipeline#andThenSink[E, Z]` — Pre-Process Sink Input
|
|
345
|
+
|
|
346
|
+
The dual of `via`: instead of transforming a stream's output, you pre-process a sink's input. Here is the signature:
|
|
347
|
+
|
|
348
|
+
```scala
|
|
349
|
+
trait Pipeline[-In, +Out] {
|
|
350
|
+
def andThenSink[E, Z](sink: Sink[E, Out, Z]): Sink[E, In, Z]
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
This is an alias for `applyToSink`. After calling `andThenSink`, the resulting sink accepts `In` elements, transforms them through the pipeline, and feeds the `Out` elements to the original sink:
|
|
355
|
+
|
|
356
|
+
```scala
|
|
357
|
+
import zio.blocks.streams.*
|
|
358
|
+
|
|
359
|
+
// A pipeline that normalizes strings
|
|
360
|
+
val normalize = Pipeline.map[String, String](_.trim.toLowerCase)
|
|
361
|
+
// normalize: Pipeline[String, String] = zio.blocks.streams.Pipeline$MapPipeline@2a3c97bf
|
|
362
|
+
|
|
363
|
+
// Apply to different sinks
|
|
364
|
+
val collectNormalized = normalize.andThenSink(Sink.collectAll[String])
|
|
365
|
+
// collectNormalized: Sink[Nothing, String, Chunk[String]] = zio.blocks.streams.Sink$Contramapped@7c1204c4
|
|
366
|
+
val countNormalized = normalize.andThenSink(Sink.count)
|
|
367
|
+
// countNormalized: Sink[Nothing, String, Long] = zio.blocks.streams.Sink$Contramapped@3a99eae2
|
|
368
|
+
|
|
369
|
+
val result = Stream(" Hello ", " WORLD ").run(collectNormalized)
|
|
370
|
+
// result: Either[Nothing, Chunk[String]] = Right(IndexedSeq("hello", "world"))
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### When to Use `andThenSink` vs `via`
|
|
374
|
+
|
|
375
|
+
Both achieve the same result. Choose based on which side you want to reuse:
|
|
376
|
+
|
|
377
|
+
| Approach | Use when… |
|
|
378
|
+
|--------------------------------------|---------------------------------------------|
|
|
379
|
+
| `stream.via(pipe).run(sink)` | You have a fixed pipeline and varying sinks |
|
|
380
|
+
| `stream.run(pipe.andThenSink(sink))` | You want a reusable "pre-processing sink" |
|
|
381
|
+
|
|
382
|
+
The laws guarantee equivalence: `stream.via(pipe).run(sink) == stream.run(pipe.andThenSink(sink))`.
|
|
383
|
+
|
|
384
|
+
### `Pipeline#applyToSink[E, Z]` — Direct Application
|
|
385
|
+
|
|
386
|
+
`andThenSink` is an alias for `applyToSink`. Here is the signature:
|
|
387
|
+
|
|
388
|
+
```scala
|
|
389
|
+
trait Pipeline[-In, +Out] {
|
|
390
|
+
def applyToSink[E, Z](sink: Sink[E, Out, Z]): Sink[E, In, Z]
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Prefer `andThenSink` for readability.
|
|
395
|
+
|
|
396
|
+
## JVM Primitive Specialization
|
|
397
|
+
|
|
398
|
+
`Pipeline.map`, `Pipeline.filter`, `Pipeline.collect`, and `Pipeline.identity` all require `JvmType.Infer[A]` implicit parameters. These are resolved at compile time and enable unboxed, specialized code paths for primitive types (`Int`, `Long`, `Float`, `Double`, etc.). You never need to provide these explicitly — the compiler infers them automatically.
|
|
399
|
+
|
|
400
|
+
`Pipeline.take` and `Pipeline.drop` do not require `JvmType.Infer` because they do not inspect or transform element values — they only count positions.
|
|
401
|
+
|
|
402
|
+
## Integration
|
|
403
|
+
|
|
404
|
+
`Pipeline` integrates seamlessly with the other core streaming primitives: `Stream` and `Sink`. Understanding these integrations shows how pipelines fit into the broader streaming architecture.
|
|
405
|
+
|
|
406
|
+
### With Stream
|
|
407
|
+
|
|
408
|
+
`Pipeline` is the mechanism behind `Stream.via`. Every call to `stream.via(pipe)` delegates to `pipe.applyToStream(stream)`, which constructs the appropriate `Stream` subtype node. See [Stream — Integration with Pipeline and Sink](./stream.md#integration-with-pipeline-and-sink) for the stream-side perspective.
|
|
409
|
+
|
|
410
|
+
### With Sink
|
|
411
|
+
|
|
412
|
+
`Pipeline.andThenSink` creates a new `Sink` that pre-processes its input through the pipeline before the original sink consumes it. The `Sink` type provides its own transformation methods (`contramap`, `map`, `mapError`) — `andThenSink` extends these with the full power of pipeline composition (filtering, taking, dropping, collecting).
|
|
413
|
+
|
|
414
|
+
## Running the Examples
|
|
415
|
+
|
|
416
|
+
All code from this guide is available as runnable examples in the `streams-examples` module.
|
|
417
|
+
|
|
418
|
+
**1. Clone the repository and navigate to the project:**
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
git clone https://github.com/zio/zio-blocks.git
|
|
422
|
+
cd zio-blocks
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**2. Run individual examples with sbt:**
|
|
426
|
+
|
|
427
|
+
### Basic Usage
|
|
428
|
+
|
|
429
|
+
This example demonstrates all six Pipeline factory methods: `map`, `filter`, `collect`, `take`, `drop`, and `identity`. Here is the source code:
|
|
430
|
+
|
|
431
|
+
```scala title="streams-examples/src/main/scala/pipeline/PipelineBasicUsageExample.scala"
|
|
432
|
+
/*
|
|
433
|
+
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
434
|
+
*
|
|
435
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
436
|
+
* you may not use this file except in compliance with the License.
|
|
437
|
+
* You may obtain a copy of the License at
|
|
438
|
+
*
|
|
439
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
440
|
+
*
|
|
441
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
442
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
443
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
444
|
+
* See the License for the specific language governing permissions and
|
|
445
|
+
* limitations under the License.
|
|
446
|
+
*/
|
|
447
|
+
|
|
448
|
+
package pipeline
|
|
449
|
+
|
|
450
|
+
import zio.blocks.streams.*
|
|
451
|
+
import zio.sbt.ExprEval.show
|
|
452
|
+
|
|
453
|
+
object PipelineBasicUsageExample extends App {
|
|
454
|
+
println("=== Pipeline Basic Usage ===\n")
|
|
455
|
+
|
|
456
|
+
// 1. Pipeline.map — transform each element
|
|
457
|
+
println("1. Pipeline.map — transform each element:")
|
|
458
|
+
val doubler = Pipeline.map[Int, Int](_ * 2)
|
|
459
|
+
show(Stream(1, 2, 3).via(doubler).runCollect)
|
|
460
|
+
|
|
461
|
+
// 2. Pipeline.map — cross-type transformation
|
|
462
|
+
println("\n2. Pipeline.map — type-changing transformation:")
|
|
463
|
+
val intToString = Pipeline.map[Int, String](n => s"item-$n")
|
|
464
|
+
show(Stream(1, 2, 3).via(intToString).runCollect)
|
|
465
|
+
|
|
466
|
+
// 3. Pipeline.filter — keep elements matching a predicate
|
|
467
|
+
println("\n3. Pipeline.filter — keep matching elements:")
|
|
468
|
+
val positives = Pipeline.filter[Int](_ > 0)
|
|
469
|
+
show(Stream(-2, -1, 0, 1, 2).via(positives).runCollect)
|
|
470
|
+
|
|
471
|
+
// 4. Pipeline.collect — partial function (filter + map)
|
|
472
|
+
println("\n4. Pipeline.collect — partial function transformation:")
|
|
473
|
+
val extractInts = Pipeline.collect[Any, Int] { case n: Int => n * 10 }
|
|
474
|
+
show(Stream(1, "a", 2, "b").via(extractInts).runCollect)
|
|
475
|
+
|
|
476
|
+
// 5. Pipeline.take — first n elements
|
|
477
|
+
println("\n5. Pipeline.take — first n elements (short-circuits):")
|
|
478
|
+
val firstThree = Pipeline.take[Int](3)
|
|
479
|
+
show(Stream.range(0, 1000).via(firstThree).runCollect)
|
|
480
|
+
|
|
481
|
+
// 6. Pipeline.drop — skip first n elements
|
|
482
|
+
println("\n6. Pipeline.drop — skip first n elements:")
|
|
483
|
+
val skipTwo = Pipeline.drop[String](2)
|
|
484
|
+
show(Stream("header", "subheader", "data1", "data2").via(skipTwo).runCollect)
|
|
485
|
+
|
|
486
|
+
// 7. Pipeline.identity — pass-through (no-op)
|
|
487
|
+
println("\n7. Pipeline.identity — pass-through (neutral element):")
|
|
488
|
+
val noOp = Pipeline.identity[Int]
|
|
489
|
+
show(Stream(1, 2, 3).via(noOp).runCollect)
|
|
490
|
+
|
|
491
|
+
// 8. Combining drop and take to get a range
|
|
492
|
+
println("\n8. Combining drop and take for pagination:")
|
|
493
|
+
val page2 = Pipeline.drop[Int](3).andThen(Pipeline.take(3))
|
|
494
|
+
show(Stream.range(0, 10).via(page2).runCollect)
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
([source](https://github.com/zio/zio-blocks/blob/main/streams-examples/src/main/scala/pipeline/PipelineBasicUsageExample.scala))
|
|
499
|
+
|
|
500
|
+
Run it with:
|
|
501
|
+
|
|
502
|
+
```bash
|
|
503
|
+
sbt "streams-examples/runMain pipeline.PipelineBasicUsageExample"
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Pipeline Composition
|
|
507
|
+
|
|
508
|
+
This example shows how to compose pipelines with `andThen`, apply them to multiple streams, and build pipelines conditionally. Here is the source code:
|
|
509
|
+
|
|
510
|
+
```scala title="streams-examples/src/main/scala/pipeline/PipelineCompositionExample.scala"
|
|
511
|
+
/*
|
|
512
|
+
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
513
|
+
*
|
|
514
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
515
|
+
* you may not use this file except in compliance with the License.
|
|
516
|
+
* You may obtain a copy of the License at
|
|
517
|
+
*
|
|
518
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
519
|
+
*
|
|
520
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
521
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
522
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
523
|
+
* See the License for the specific language governing permissions and
|
|
524
|
+
* limitations under the License.
|
|
525
|
+
*/
|
|
526
|
+
|
|
527
|
+
package pipeline
|
|
528
|
+
|
|
529
|
+
import zio.blocks.streams.*
|
|
530
|
+
import zio.sbt.ExprEval.show
|
|
531
|
+
|
|
532
|
+
object PipelineCompositionExample extends App {
|
|
533
|
+
println("=== Pipeline Composition ===\n")
|
|
534
|
+
|
|
535
|
+
// 1. Basic andThen composition
|
|
536
|
+
println("1. Composing filter + map with andThen:")
|
|
537
|
+
val filterPositive = Pipeline.filter[Int](_ > 0)
|
|
538
|
+
val double = Pipeline.map[Int, Int](_ * 2)
|
|
539
|
+
val composed = filterPositive.andThen(double)
|
|
540
|
+
|
|
541
|
+
show(Stream(-3, -1, 0, 2, 5).via(composed).runCollect)
|
|
542
|
+
|
|
543
|
+
// 2. Multi-stage pipeline
|
|
544
|
+
println("\n2. Multi-stage pipeline (filter → map → take):")
|
|
545
|
+
val multiStage = Pipeline
|
|
546
|
+
.filter[Int](_ % 2 == 0)
|
|
547
|
+
.andThen(Pipeline.map[Int, Int](_ * 10))
|
|
548
|
+
.andThen(Pipeline.take(3))
|
|
549
|
+
|
|
550
|
+
show(Stream.range(1, 11).via(multiStage).runCollect)
|
|
551
|
+
|
|
552
|
+
// 3. Reusing the same pipeline across different streams
|
|
553
|
+
println("\n3. Reusing the same pipeline across different streams:")
|
|
554
|
+
val normalize = Pipeline.filter[Int](_ >= 0).andThen(Pipeline.map[Int, Double](_.toDouble / 100.0))
|
|
555
|
+
|
|
556
|
+
val dataset1 = Stream(150, -20, 75, 200, -10)
|
|
557
|
+
val dataset2 = Stream(50, 100, -5, 300)
|
|
558
|
+
|
|
559
|
+
show(dataset1.via(normalize).runCollect)
|
|
560
|
+
show(dataset2.via(normalize).runCollect)
|
|
561
|
+
|
|
562
|
+
// 4. Category law: left identity
|
|
563
|
+
println("\n4. Category law — left identity (identity andThen p == p):")
|
|
564
|
+
val pipe = Pipeline.map[Int, Int](_ + 1)
|
|
565
|
+
val data = Stream(1, 2, 3)
|
|
566
|
+
|
|
567
|
+
val withIdentityL = data.via(Pipeline.identity[Int].andThen(pipe)).runCollect
|
|
568
|
+
val withoutIdentity = data.via(pipe).runCollect
|
|
569
|
+
show(withIdentityL)
|
|
570
|
+
show(withoutIdentity)
|
|
571
|
+
|
|
572
|
+
// 5. Category law: right identity
|
|
573
|
+
println("\n5. Category law — right identity (p andThen identity == p):")
|
|
574
|
+
val withIdentityR = data.via(pipe.andThen(Pipeline.identity[Int])).runCollect
|
|
575
|
+
show(withIdentityR)
|
|
576
|
+
show(withoutIdentity)
|
|
577
|
+
|
|
578
|
+
// 6. Category law: associativity
|
|
579
|
+
println("\n6. Category law — associativity ((p andThen q) andThen r == p andThen (q andThen r)):")
|
|
580
|
+
val p = Pipeline.filter[Int](_ > 0)
|
|
581
|
+
val q = Pipeline.map[Int, Int](_ * 3)
|
|
582
|
+
val r = Pipeline.take[Int](2)
|
|
583
|
+
|
|
584
|
+
val leftGrouped = (p.andThen(q)).andThen(r)
|
|
585
|
+
val rightGrouped = p.andThen(q.andThen(r))
|
|
586
|
+
|
|
587
|
+
val source = Stream(-1, 2, -3, 4, 5, 6)
|
|
588
|
+
show(source.via(leftGrouped).runCollect)
|
|
589
|
+
show(source.via(rightGrouped).runCollect)
|
|
590
|
+
|
|
591
|
+
// 7. Building pipelines conditionally
|
|
592
|
+
println("\n7. Building pipelines conditionally:")
|
|
593
|
+
def buildPipeline(limit: Option[Int], onlyPositive: Boolean): Pipeline[Int, Int] = {
|
|
594
|
+
var pipe: Pipeline[Int, Int] = Pipeline.identity[Int]
|
|
595
|
+
if (onlyPositive) pipe = pipe.andThen(Pipeline.filter(_ > 0))
|
|
596
|
+
limit.foreach(n => pipe = pipe.andThen(Pipeline.take(n.toLong)))
|
|
597
|
+
pipe
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
val conditionalPipe = buildPipeline(limit = Some(3), onlyPositive = true)
|
|
601
|
+
show(Stream(-1, 2, -3, 4, 5, 6).via(conditionalPipe).runCollect)
|
|
602
|
+
|
|
603
|
+
val noPipe = buildPipeline(limit = None, onlyPositive = false)
|
|
604
|
+
show(Stream(-1, 2, -3).via(noPipe).runCollect)
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
([source](https://github.com/zio/zio-blocks/blob/main/streams-examples/src/main/scala/pipeline/PipelineCompositionExample.scala))
|
|
609
|
+
|
|
610
|
+
Run it with:
|
|
611
|
+
|
|
612
|
+
```bash
|
|
613
|
+
sbt "streams-examples/runMain pipeline.PipelineCompositionExample"
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
### Sink Integration
|
|
617
|
+
|
|
618
|
+
This example demonstrates applying pipelines to sinks with `andThenSink`, showing the equivalence between `stream.via(pipe).run(sink)` and `stream.run(pipe.andThenSink(sink))`. Here is the source code:
|
|
619
|
+
|
|
620
|
+
```scala title="streams-examples/src/main/scala/pipeline/PipelineSinkIntegrationExample.scala"
|
|
621
|
+
/*
|
|
622
|
+
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
623
|
+
*
|
|
624
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
625
|
+
* you may not use this file except in compliance with the License.
|
|
626
|
+
* You may obtain a copy of the License at
|
|
627
|
+
*
|
|
628
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
629
|
+
*
|
|
630
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
631
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
632
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
633
|
+
* See the License for the specific language governing permissions and
|
|
634
|
+
* limitations under the License.
|
|
635
|
+
*/
|
|
636
|
+
|
|
637
|
+
package pipeline
|
|
638
|
+
|
|
639
|
+
import zio.blocks.streams.*
|
|
640
|
+
import zio.sbt.ExprEval.show
|
|
641
|
+
|
|
642
|
+
object PipelineSinkIntegrationExample extends App {
|
|
643
|
+
println("=== Pipeline ↔ Sink Integration ===\n")
|
|
644
|
+
|
|
645
|
+
// 1. Basic andThenSink usage
|
|
646
|
+
println("1. Basic andThenSink — pre-process before collecting:")
|
|
647
|
+
val doubler = Pipeline.map[Int, Int](_ * 2)
|
|
648
|
+
val collectDoubled = doubler.andThenSink(Sink.collectAll[Int])
|
|
649
|
+
|
|
650
|
+
show(Stream(1, 2, 3).run(collectDoubled))
|
|
651
|
+
|
|
652
|
+
// 2. Equivalence law: via + run == run + andThenSink
|
|
653
|
+
println("\n2. Equivalence law: stream.via(p).run(sink) == stream.run(p.andThenSink(sink)):")
|
|
654
|
+
val pipe = Pipeline.filter[Int](_ > 2).andThen(Pipeline.map[Int, Int](_ * 10))
|
|
655
|
+
val source = Stream(1, 2, 3, 4, 5)
|
|
656
|
+
val sink = Sink.collectAll[Int]
|
|
657
|
+
|
|
658
|
+
val viaResult = source.via(pipe).run(sink)
|
|
659
|
+
val andThenSinkResult = source.run(pipe.andThenSink(sink))
|
|
660
|
+
show(viaResult)
|
|
661
|
+
show(andThenSinkResult)
|
|
662
|
+
|
|
663
|
+
// 3. Reusable pre-processing sink
|
|
664
|
+
println("\n3. Reusable pre-processing sink:")
|
|
665
|
+
val cleanString = Pipeline.map[String, String](_.trim.toLowerCase)
|
|
666
|
+
val collectCleaned = cleanString.andThenSink(Sink.collectAll[String])
|
|
667
|
+
val countCleaned = cleanString.andThenSink(Sink.count)
|
|
668
|
+
|
|
669
|
+
val rawData = Stream(" Hello ", " WORLD ", " Scala ")
|
|
670
|
+
|
|
671
|
+
show(rawData.run(collectCleaned))
|
|
672
|
+
show(rawData.run(countCleaned))
|
|
673
|
+
|
|
674
|
+
// 4. andThenSink with foldLeft
|
|
675
|
+
println("\n4. Pipeline + foldLeft sink:")
|
|
676
|
+
val sumPositives = Pipeline
|
|
677
|
+
.filter[Int](_ > 0)
|
|
678
|
+
.andThenSink(Sink.foldLeft(0)((acc, x) => acc + x))
|
|
679
|
+
|
|
680
|
+
show(Stream(-5, 3, -2, 7, 1).run(sumPositives))
|
|
681
|
+
|
|
682
|
+
// 5. andThenSink with head/find
|
|
683
|
+
println("\n5. Pipeline + head/find sinks:")
|
|
684
|
+
val firstEven = Pipeline.filter[Int](_ % 2 == 0).andThenSink(Sink.head[Int])
|
|
685
|
+
|
|
686
|
+
show(Stream(1, 3, 4, 6).run(firstEven))
|
|
687
|
+
|
|
688
|
+
// 6. Multiple pipelines, same sink
|
|
689
|
+
println("\n6. Multiple pipelines applied to the same sink:")
|
|
690
|
+
val baseSink = Sink.collectAll[Int]
|
|
691
|
+
|
|
692
|
+
val evens = Pipeline.filter[Int](_ % 2 == 0).andThenSink(baseSink)
|
|
693
|
+
val odds = Pipeline.filter[Int](_ % 2 != 0).andThenSink(baseSink)
|
|
694
|
+
|
|
695
|
+
val nums = Stream(1, 2, 3, 4, 5, 6)
|
|
696
|
+
show(nums.run(evens))
|
|
697
|
+
show(nums.run(odds))
|
|
698
|
+
|
|
699
|
+
// 7. Complex pipeline applied to sink
|
|
700
|
+
println("\n7. Multi-stage pipeline applied to sink:")
|
|
701
|
+
val processingPipe = Pipeline
|
|
702
|
+
.filter[Int](_ > 0)
|
|
703
|
+
.andThen(Pipeline.map[Int, Int](_ * 2))
|
|
704
|
+
.andThen(Pipeline.take(3))
|
|
705
|
+
|
|
706
|
+
val processedSum = processingPipe.andThenSink(Sink.foldLeft(0)(_ + _))
|
|
707
|
+
|
|
708
|
+
show(Stream(-1, 5, 3, 8, 2, 9).run(processedSum))
|
|
709
|
+
}
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
([source](https://github.com/zio/zio-blocks/blob/main/streams-examples/src/main/scala/pipeline/PipelineSinkIntegrationExample.scala))
|
|
713
|
+
|
|
714
|
+
Run it with:
|
|
715
|
+
|
|
716
|
+
```bash
|
|
717
|
+
sbt "streams-examples/runMain pipeline.PipelineSinkIntegrationExample"
|
|
718
|
+
```
|