@zio.dev/zio-blocks 0.0.51 → 0.0.56
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/adr/2026-07-18-data-migration.md +123 -0
- package/guides/async-getting-started.md +687 -0
- package/guides/compile-time-resource-safety-with-scope.md +6 -0
- package/guides/getting-started-with-mux.md +0 -112
- package/guides/query-dsl-extending.md +1 -1
- package/guides/query-dsl-fluent-builder.md +1 -1
- package/guides/query-dsl-reified-optics.md +1 -1
- package/guides/query-dsl-sql.md +395 -1
- package/guides/sql-checked-interpolation.md +173 -0
- package/guides/sql-transactions.md +286 -0
- package/guides/telemetry-guide.md +131 -70
- package/guides/zio-schema-migration.md +6 -6
- package/index.md +200 -559
- package/package.json +1 -1
- package/reference/async.md +1379 -531
- package/reference/chunk.md +3 -3
- package/reference/codegen/index.md +1 -1
- package/reference/combinators.md +4 -4
- package/reference/config/config-decoder.md +460 -0
- package/reference/config/config-source.md +489 -0
- package/reference/config/errors.md +278 -0
- package/reference/config/flags.md +369 -0
- package/reference/config/formats.md +314 -0
- package/reference/config/index.md +304 -0
- package/reference/config/rollout.md +336 -0
- package/reference/context.md +6 -49
- package/reference/data-migration.md +269 -0
- package/reference/datastar/attributes.md +302 -0
- package/reference/datastar/events.md +234 -0
- package/reference/datastar/index.md +256 -0
- package/reference/datastar/signals.md +230 -0
- package/reference/datastar/sse.md +295 -0
- package/reference/datastar.md +2 -2
- package/reference/docs.md +2 -2
- package/reference/endpoint/bulk-creation.md +96 -0
- package/reference/endpoint/endpoint.md +1 -0
- package/reference/endpoint/index.md +9 -89
- package/reference/endpoint/path-codec.md +12 -24
- package/reference/endpoint/route-pattern.md +4 -6
- package/reference/endpoint/segment-codec.md +19 -32
- package/reference/html.md +313 -9
- package/reference/htmx/index.md +4 -52
- package/reference/htmx/response-headers.md +240 -0
- package/reference/http-model/headers.md +735 -0
- package/reference/http-model/index.md +3 -1
- package/reference/http-model/model.md +107 -71
- package/reference/http-model/schema-codecs.md +522 -0
- package/reference/http-model/schema.md +6 -3
- package/reference/http-model/server-sent-event.md +341 -0
- package/reference/jwt.md +195 -0
- package/reference/maybe.md +128 -11
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +7 -2
- package/reference/openapi.md +3 -3
- package/reference/projection.md +654 -0
- package/reference/resource-management/index.md +1 -1
- package/reference/resource-management/resource.md +2 -98
- package/reference/resource-management/scope.md +1 -209
- package/reference/resource-management/wire.md +4 -50
- package/reference/ringbuffer/advanced.mdx +1 -1
- package/reference/ringbuffer/index.mdx +3 -3
- package/reference/ringbuffer/mpmc.mdx +38 -4
- package/reference/ringbuffer/mpsc.mdx +36 -4
- package/reference/ringbuffer/spmc.mdx +1 -1
- package/reference/ringbuffer/spsc.mdx +87 -15
- package/reference/schema/allows.md +0 -96
- package/reference/schema/binding.md +2 -2
- package/reference/schema/built-in-codecs/avro.md +2 -2
- package/reference/schema/built-in-codecs/bson.md +50 -20
- package/reference/schema/built-in-codecs/csv.md +2 -2
- package/reference/schema/built-in-codecs/index.md +3 -3
- package/reference/schema/built-in-codecs/json/index.md +2 -2
- package/reference/schema/built-in-codecs/json/json.md +1 -0
- package/reference/schema/built-in-codecs/messagepack.md +3 -3
- package/reference/schema/built-in-codecs/thrift.md +2 -2
- package/reference/schema/built-in-codecs/toon.md +3 -3
- package/reference/schema/built-in-codecs/yaml.md +2 -2
- package/reference/schema/codec.md +11 -11
- package/reference/schema/dynamic-optic.md +48 -3
- package/reference/schema/dynamic-schema.md +3 -3
- package/reference/schema/index.md +2 -0
- package/reference/schema/path-interpolator.md +2 -0
- package/reference/schema/reflect-transformer.md +140 -0
- package/reference/schema/schema-evolution/as.md +4 -4
- package/reference/schema/schema-evolution/into.md +2 -2
- package/reference/schema/schema-expr.md +2 -2
- package/reference/schema/schema-search.md +263 -0
- package/reference/schema/schema.md +10 -2
- package/reference/schema/type-class-derivation.md +1 -1
- package/reference/smithy.md +502 -3
- package/reference/sql/db-codec-deriver.md +3 -3
- package/reference/sql/db-codec.md +22 -22
- package/reference/sql/db-con.md +4 -4
- package/reference/sql/db-connection.md +1 -1
- package/reference/sql/db-param.md +1 -1
- package/reference/sql/db-result-reader.md +4 -2
- package/reference/sql/db-tx.md +46 -14
- package/reference/sql/ddl.md +1 -1
- package/reference/sql/frag.md +44 -10
- package/reference/sql/index.md +7 -7
- package/reference/sql/repo.md +15 -15
- package/reference/sql/sql-dialect.md +1 -1
- package/reference/sql/sql-logger.md +1 -1
- package/reference/sql/sql-name-mapper.md +3 -3
- package/reference/sql/table-metadata.md +3 -3
- package/reference/sql/table.md +10 -10
- package/reference/sql/transactor-zio.md +1 -1
- package/reference/sql/transactor.md +21 -11
- package/reference/sql-zio.md +2 -2
- package/reference/streams/core/index.md +32 -0
- package/reference/streams/{pipeline.md → core/pipeline.md} +210 -74
- package/reference/streams/{sink.md → core/sink.md} +331 -353
- package/reference/streams/{stream.md → core/stream.md} +919 -209
- package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
- package/reference/streams/execution-and-compatibility/index.md +35 -0
- package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
- package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
- package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
- package/reference/streams/index.md +140 -67
- package/reference/streams/primitives/index.md +30 -0
- package/reference/streams/primitives/reader.md +1992 -0
- package/reference/streams/{writer.md → primitives/writer.md} +254 -98
- package/reference/telemetry/common/any-value.md +90 -0
- package/reference/telemetry/common/attribute-key.md +87 -0
- package/reference/telemetry/common/attributes.md +118 -0
- package/reference/telemetry/common/index.md +39 -0
- package/reference/telemetry/common/instrumentation-scope.md +24 -0
- package/reference/telemetry/common/resource.md +34 -0
- package/reference/telemetry/index.md +311 -0
- package/reference/telemetry/logging/index.md +197 -0
- package/reference/telemetry/logging/log-enrichment.md +72 -0
- package/reference/telemetry/logging/log-formatter.md +100 -0
- package/reference/telemetry/logging/log-record-processor.md +56 -0
- package/reference/telemetry/logging/log-record.md +44 -0
- package/reference/telemetry/logging/log-writer.md +64 -0
- package/reference/telemetry/logging/logger-provider.md +142 -0
- package/reference/telemetry/logging/logger.md +83 -0
- package/reference/telemetry/logging/severity.md +62 -0
- package/reference/telemetry/metrics/index.md +150 -0
- package/reference/telemetry/metrics/instruments.md +183 -0
- package/reference/telemetry/metrics/labeled-instruments.md +74 -0
- package/reference/telemetry/metrics/meter-provider.md +76 -0
- package/reference/telemetry/metrics/meter.md +98 -0
- package/reference/telemetry/metrics/metric-data.md +57 -0
- package/reference/telemetry/otel/custom-exporter.md +216 -0
- package/reference/telemetry/otel/index.md +212 -0
- package/reference/telemetry/tracing/index.md +155 -0
- package/reference/telemetry/tracing/sampler.md +89 -0
- package/reference/telemetry/tracing/span-builder.md +57 -0
- package/reference/telemetry/tracing/span-context.md +39 -0
- package/reference/telemetry/tracing/span-data.md +32 -0
- package/reference/telemetry/tracing/span-kind.md +55 -0
- package/reference/telemetry/tracing/span-processor.md +53 -0
- package/reference/telemetry/tracing/span-status.md +47 -0
- package/reference/telemetry/tracing/span.md +117 -0
- package/reference/telemetry/tracing/tracer-provider.md +91 -0
- package/reference/telemetry/tracing/tracer.md +52 -0
- package/reference/typeid.md +0 -64
- package/sidebars.js +365 -185
- package/undocumented-report.md +528 -270
- package/reference/config.md +0 -158
- package/reference/streams/concurrent-operators.md +0 -106
- package/reference/streams/reader.md +0 -1284
- package/reference/streams/scala-2-compatibility.md +0 -55
- package/reference/streams/zero-boxing.md +0 -275
- package/reference/telemetry.md +0 -693
|
@@ -1,1284 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: reader
|
|
3
|
-
title: "Reader"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
`Reader[+Elem]` is the **pull-based source that powers ZIO Blocks streams**. When you call a terminal operation like `stream.run(sink)`, the stream compiles into a `Reader`, which yields values one at a time on demand until closed.
|
|
7
|
-
|
|
8
|
-
The fundamental operations are `read(sentinel)` — returns the next element or a sentinel when exhausted — and `close()` — signals stream end and releases resources. Most users never interact with `Reader` directly, but understanding it clarifies how streams work internally.
|
|
9
|
-
|
|
10
|
-
The compilation and execution flow:
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
Stream[E, A] ──(compile)──> Reader[A]
|
|
14
|
-
│
|
|
15
|
-
└─(drain via Sink)──> Either[E, Z]
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
`Reader`:
|
|
19
|
-
- Is lazy and pull-based — `Stream` transformations don't run until `read()` is called, running in constant space one element at a time
|
|
20
|
-
- Is not thread-safe — designed for single-threaded consumption
|
|
21
|
-
- Uses a sentinel protocol where callers specify the end-of-stream value; for primitives, specialized methods like `Reader#readInt(sentinel)` avoid boxing entirely
|
|
22
|
-
- Dispatches on `Reader#jvmType` to use specialized, unboxed reads for primitive types
|
|
23
|
-
- Is the compilation target of `Stream` — when a stream runs, it becomes a `Reader`
|
|
24
|
-
- Guarantees resource safety by tracking and closing files, database connections, and buffers via `finally` blocks, even if consumption stops early or fails
|
|
25
|
-
- Supports composition by chaining readers through transformations without materializing intermediate data
|
|
26
|
-
|
|
27
|
-
Here is the core `Reader` interface with the most essential methods:
|
|
28
|
-
|
|
29
|
-
```scala
|
|
30
|
-
abstract class Reader[+Elem] {
|
|
31
|
-
def read[A >: Elem](sentinel: A): A
|
|
32
|
-
def close(): Unit
|
|
33
|
-
def isClosed: Boolean
|
|
34
|
-
def readable(): Boolean
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## Quick Showcase
|
|
39
|
-
|
|
40
|
-
Here's how to create and drain a `Reader`:
|
|
41
|
-
|
|
42
|
-
```scala
|
|
43
|
-
import zio.blocks.streams.io.Reader
|
|
44
|
-
import zio.blocks.chunk.Chunk
|
|
45
|
-
import scala.collection.mutable.Buffer
|
|
46
|
-
|
|
47
|
-
val r = Reader.fromChunk(Chunk(1, 2, 3, 4, 5))
|
|
48
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$FromChunkInt@2305ca8e
|
|
49
|
-
val collected = Buffer[Int]()
|
|
50
|
-
// collected: Buffer[Int] = ArrayBuffer(1, 2, 3, 4, 5)
|
|
51
|
-
|
|
52
|
-
// Pull elements until sentinel
|
|
53
|
-
def drainAll(): Unit = {
|
|
54
|
-
val elem = r.read(-1)
|
|
55
|
-
if (elem != -1) {
|
|
56
|
-
collected += elem
|
|
57
|
-
drainAll()
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
drainAll()
|
|
61
|
-
|
|
62
|
-
println(s"Collected: $collected")
|
|
63
|
-
// Collected: ArrayBuffer(1, 2, 3, 4, 5)
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Motivation
|
|
67
|
-
|
|
68
|
-
Imagine you're processing a massive CSV file—millions of rows of customer data. Your first instinct is to load it all into memory as a `List[Row]`, transform it, filter it, and then write the results. This works fine for small files, but one day someone feeds you a 50GB dataset and your application crashes with `OutOfMemoryError`. You've hit the fundamental problem of eager evaluation: **you must load everything before you can do anything**, and if the data is bigger than available memory, you're stuck.
|
|
69
|
-
|
|
70
|
-
Even if you manage to fit the data in memory, you've paid the startup cost upfront. If your pipeline only needs the first 100 rows to produce a result, you've wasted time and energy materializing the other millions. And if something fails partway through—a database connection drops, a file is corrupted—you've already consumed resources and may have inconsistencies to clean up.
|
|
71
|
-
|
|
72
|
-
The streaming intuition is different: instead of pulling all data at once, what if the consumer asked the producer "give me the next element?" one at a time? This way, you never hold more than one element in memory, you only do work on elements you actually use, and you can stop immediately when you have enough.
|
|
73
|
-
|
|
74
|
-
`Reader` embodies this pull-based philosophy. Rather than materializing a `List`, a `Stream` compiles down to a `Reader`—a stateful object that produces one element each time you call `read()`. The consumer (a `Sink`) drives the pace: it calls `read()` when ready, and the `Reader` computes and returns the next value. When the stream is exhausted, `Reader` returns a sentinel—a special value you provide—signaling "no more data." No exceptions, no null, no boxing overhead.
|
|
75
|
-
|
|
76
|
-
`Reader` shines when you're processing large, unbounded, or expensive-to-produce data sources: database result sets, network streams, log files, sensor data, or any pipeline where memory or time efficiency matters. Instead of hoping your data fits in memory, you pay a constant, predictable cost per element.
|
|
77
|
-
|
|
78
|
-
## Construction
|
|
79
|
-
|
|
80
|
-
Several ways to create a `Reader`, from predefined singletons to collections and I/O sources:
|
|
81
|
-
|
|
82
|
-
### Creating Predefined Readers
|
|
83
|
-
|
|
84
|
-
`Reader.closed` — An already-closed reader that emits no elements. Useful as a base case or for empty streams:
|
|
85
|
-
|
|
86
|
-
```scala
|
|
87
|
-
object Reader {
|
|
88
|
-
def closed: Reader[Nothing]
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Here's how to create and use a closed reader:
|
|
93
|
-
|
|
94
|
-
```scala
|
|
95
|
-
import zio.blocks.streams.io.Reader
|
|
96
|
-
|
|
97
|
-
val r = Reader.closed
|
|
98
|
-
// r: Reader[Nothing] = zio.blocks.streams.io.Reader$ClosedReader$@55f2a900
|
|
99
|
-
println(r.isClosed) // true
|
|
100
|
-
// true
|
|
101
|
-
println(r.read(-1)) // -1 (the sentinel)
|
|
102
|
-
// -1
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### From Collections
|
|
106
|
-
|
|
107
|
-
`Reader.fromChunk` — Creates a reader backed by a `Chunk`. Dispatches on the element type to use specialized, unboxed reads for primitives:
|
|
108
|
-
|
|
109
|
-
```scala
|
|
110
|
-
object Reader {
|
|
111
|
-
def fromChunk[A](chunk: Chunk[A])(implicit jt: JvmType.Infer[A]): Reader[A]
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
Create a reader from a chunk and drain its elements:
|
|
116
|
-
|
|
117
|
-
```scala
|
|
118
|
-
import zio.blocks.streams.io.Reader
|
|
119
|
-
import zio.blocks.chunk.Chunk
|
|
120
|
-
|
|
121
|
-
val chunk = Chunk(10, 20, 30)
|
|
122
|
-
// chunk: Chunk[Int] = IndexedSeq(10, 20, 30)
|
|
123
|
-
val r = Reader.fromChunk(chunk)
|
|
124
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$FromChunkInt@8388986
|
|
125
|
-
|
|
126
|
-
def drain(): Unit = {
|
|
127
|
-
val v = r.read(-1)
|
|
128
|
-
if (v != -1) {
|
|
129
|
-
println(v)
|
|
130
|
-
drain()
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
drain()
|
|
134
|
-
// 10
|
|
135
|
-
// 20
|
|
136
|
-
// 30
|
|
137
|
-
// Output: 10, 20, 30
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
`Reader.fromIterable` — Creates a reader from any `Iterable`. Works with lists, sets, vectors, and other collections:
|
|
141
|
-
|
|
142
|
-
```scala
|
|
143
|
-
object Reader {
|
|
144
|
-
def fromIterable[A](it: Iterable[A]): Reader[A]
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
Create a reader from a list and consume its elements:
|
|
149
|
-
|
|
150
|
-
```scala
|
|
151
|
-
import zio.blocks.streams.io.Reader
|
|
152
|
-
|
|
153
|
-
val list = List("a", "b", "c")
|
|
154
|
-
// list: List[String] = List("a", "b", "c")
|
|
155
|
-
val r = Reader.fromIterable(list)
|
|
156
|
-
// r: Reader[String] = zio.blocks.streams.io.Reader$FromIterable@76a099be
|
|
157
|
-
|
|
158
|
-
def drain(): Unit = {
|
|
159
|
-
val v = r.read(null)
|
|
160
|
-
if (v != null) {
|
|
161
|
-
println(v)
|
|
162
|
-
drain()
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
drain()
|
|
166
|
-
// a
|
|
167
|
-
// b
|
|
168
|
-
// c
|
|
169
|
-
// Output: a, b, c
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
`Reader.fromRange` — Creates a reader from a Scala `Range`. Optimized for integer ranges without allocation:
|
|
173
|
-
|
|
174
|
-
```scala
|
|
175
|
-
object Reader {
|
|
176
|
-
def fromRange(range: Range): Reader[Int]
|
|
177
|
-
}
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
Create a reader from a range and drain the integers:
|
|
181
|
-
|
|
182
|
-
```scala
|
|
183
|
-
import zio.blocks.streams.io.Reader
|
|
184
|
-
|
|
185
|
-
val r = Reader.fromRange(1 to 5)
|
|
186
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$FromRange@5a4e4971
|
|
187
|
-
|
|
188
|
-
def drain(): Unit = {
|
|
189
|
-
val v = r.read(-1)
|
|
190
|
-
if (v != -1) {
|
|
191
|
-
println(v)
|
|
192
|
-
drain()
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
drain()
|
|
196
|
-
// 1
|
|
197
|
-
// 2
|
|
198
|
-
// 3
|
|
199
|
-
// 4
|
|
200
|
-
// 5
|
|
201
|
-
// Output: 1, 2, 3, 4, 5
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### From I/O
|
|
205
|
-
|
|
206
|
-
`Reader.fromInputStream` — Wraps a `java.io.InputStream` as a `Reader[Int]`, where each byte is widened to `Int` (0–255). This avoids boxing on `.map`/`.filter` since `Function1` is specialized for `Int`:
|
|
207
|
-
|
|
208
|
-
```scala
|
|
209
|
-
object Reader {
|
|
210
|
-
def fromInputStream(is: InputStream): Reader[Int]
|
|
211
|
-
}
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
`Reader.fromReader` — Wraps a `java.io.Reader` as a `Reader[Char]` for character-based I/O:
|
|
215
|
-
|
|
216
|
-
```scala
|
|
217
|
-
object Reader {
|
|
218
|
-
def fromReader(r: java.io.Reader): Reader[Char]
|
|
219
|
-
}
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### Single Element
|
|
223
|
-
|
|
224
|
-
`Reader.single` — Creates a reader that emits exactly one element, then closes. Primitive types use specialized variants for zero-boxing:
|
|
225
|
-
|
|
226
|
-
```scala
|
|
227
|
-
object Reader {
|
|
228
|
-
def single[A](value: A)(implicit jt: JvmType.Infer[A]): Reader[A]
|
|
229
|
-
def singleInt(value: Int): Reader[Int]
|
|
230
|
-
def singleLong(value: Long): Reader[Long]
|
|
231
|
-
def singleFloat(value: Float): Reader[Float]
|
|
232
|
-
def singleDouble(value: Double): Reader[Double]
|
|
233
|
-
def singleChar(value: Char): Reader[Char]
|
|
234
|
-
def singleShort(value: Short): Reader[Short]
|
|
235
|
-
def singleByte(value: Byte): Reader[Int]
|
|
236
|
-
def singleBoolean(value: Boolean): Reader[Boolean]
|
|
237
|
-
}
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
When you use `Reader.single`, behavior differs between reference types and primitives. The `JvmType.Infer[A]` implicit parameter enables compile-time type detection, automatically selecting the appropriate implementation (specialized primitive or reference-type generic).
|
|
241
|
-
|
|
242
|
-
For reference types like String, `Reader.single("hello")` stores the element directly and uses an internal sentinel object (`EndOfStream`) to signal end-of-stream. You read via the generic `Reader#read[A](sentinel)` method, passing your own sentinel value. On the first call, you get your string; on subsequent calls, you receive the sentinel you provided, allowing you to detect stream closure.
|
|
243
|
-
|
|
244
|
-
For primitive types, `Reader.single(42)` could naively box the integer, but the library avoids this penalty entirely via `SingletonPrim`—a zero-boxing specialization that stores the primitive unboxed in memory. The `JvmType.Infer` implicit detects this at compile time and routes you through specialized factory methods (`Reader.singleInt`, `Reader.singleLong`, etc.) and specialized read methods (`Reader#readInt`, `Reader#readLong`, etc.). Both storage and retrieval stay unboxed, maintaining zero-copy efficiency.
|
|
245
|
-
|
|
246
|
-
Note: `Reader.singleByte` returns `Reader[Int]` (not `Reader[Byte]`) because Java's primitive byte type is typically widened to int in arrays and I/O contexts; this aligns with JVM conventions for byte-level operations. When reading, use `Reader#readInt(sentinel: Long): Long`, which returns a long to maintain the sentinel protocol—extract the int via casting if needed.
|
|
247
|
-
|
|
248
|
-
Create and read from a single-element reference-type reader with a custom sentinel:
|
|
249
|
-
|
|
250
|
-
```scala
|
|
251
|
-
import zio.blocks.streams.io.Reader
|
|
252
|
-
|
|
253
|
-
val r = Reader.single("hello")
|
|
254
|
-
// r: Reader[String] = zio.blocks.streams.io.Reader$SingletonGeneric@4ccaf3f7
|
|
255
|
-
val sentinel = "END"
|
|
256
|
-
// sentinel: String = "END"
|
|
257
|
-
println(r.read(sentinel)) // hello
|
|
258
|
-
// hello
|
|
259
|
-
println(r.read(sentinel)) // END (sentinel, reader is closed)
|
|
260
|
-
// END
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
For primitive types, use the specialized factory and read methods. The `Reader#readInt` method takes a `Long` sentinel (to avoid confusion with sentinel values that fit in int range) and returns `Long` so you can distinguish the actual int value from the sentinel:
|
|
264
|
-
|
|
265
|
-
```scala
|
|
266
|
-
import zio.blocks.streams.io.Reader
|
|
267
|
-
|
|
268
|
-
val r = Reader.singleInt(100)
|
|
269
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$SingletonPrim@56f86b9b
|
|
270
|
-
val sentinel = Long.MinValue
|
|
271
|
-
// sentinel: Long = -9223372036854775808L
|
|
272
|
-
val v1 = r.readInt(sentinel)
|
|
273
|
-
// v1: Long = 100L
|
|
274
|
-
println(v1) // 100
|
|
275
|
-
// 100
|
|
276
|
-
val v2 = r.readInt(sentinel)
|
|
277
|
-
// v2: Long = -9223372036854775808L
|
|
278
|
-
println(v2) // -9223372036854775808 (sentinel, reader is closed)
|
|
279
|
-
// -9223372036854775808
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
### Infinite & Repeating
|
|
283
|
-
|
|
284
|
-
`Reader.repeat` — Creates an infinite reader that always emits the same value:
|
|
285
|
-
|
|
286
|
-
```scala
|
|
287
|
-
object Reader {
|
|
288
|
-
def repeat[A](a: A)(implicit jt: JvmType.Infer[A]): Reader[A]
|
|
289
|
-
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
Create an infinite reader that repeatedly emits the same value:
|
|
293
|
-
|
|
294
|
-
```scala
|
|
295
|
-
import zio.blocks.streams.io.Reader
|
|
296
|
-
|
|
297
|
-
val r = Reader.repeat(1)
|
|
298
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$SingletonPrim@7c91cdb2
|
|
299
|
-
|
|
300
|
-
def drainN(n: Int): Unit = {
|
|
301
|
-
if (n > 0) {
|
|
302
|
-
val v = r.read(-1)
|
|
303
|
-
println(v)
|
|
304
|
-
drainN(n - 1)
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
drainN(3)
|
|
308
|
-
// 1
|
|
309
|
-
// 1
|
|
310
|
-
// 1
|
|
311
|
-
// Output: 1, 1, 1
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
`Reader.repeated` — Restarts an inner reader each time it closes cleanly. Used by `Stream.repeated` to create indefinitely repeating streams:
|
|
315
|
-
|
|
316
|
-
```scala
|
|
317
|
-
object Reader {
|
|
318
|
-
def repeated[A](inner: Reader[A]): Reader[A]
|
|
319
|
-
}
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
### Unfold (State Machine)
|
|
323
|
-
|
|
324
|
-
`Reader.unfold` — Creates a reader by unfolding state with a function. Returns `None` to signal completion, or `Some((elem, nextState))` to emit an element and advance state:
|
|
325
|
-
|
|
326
|
-
```scala
|
|
327
|
-
object Reader {
|
|
328
|
-
def unfold[S, A](s: S)(f: S => Option[(A, S)]): Reader[A]
|
|
329
|
-
}
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
Create a reader that unfolds state incrementally until completion:
|
|
333
|
-
|
|
334
|
-
```scala
|
|
335
|
-
import zio.blocks.streams.io.Reader
|
|
336
|
-
|
|
337
|
-
val r = Reader.unfold(1) { s =>
|
|
338
|
-
if (s > 3) None else Some((s, s + 1))
|
|
339
|
-
}
|
|
340
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$Unfold@65f92a5f
|
|
341
|
-
|
|
342
|
-
def drain(): Unit = {
|
|
343
|
-
val v = r.read(-1)
|
|
344
|
-
if (v != -1) {
|
|
345
|
-
println(v)
|
|
346
|
-
drain()
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
drain()
|
|
350
|
-
// 1
|
|
351
|
-
// 2
|
|
352
|
-
// 3
|
|
353
|
-
// Output: 1, 2, 3
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
## Core Operations
|
|
357
|
-
|
|
358
|
-
These methods form the primary interface for consuming elements and querying reader state:
|
|
359
|
-
|
|
360
|
-
### Pulling Elements
|
|
361
|
-
|
|
362
|
-
`Reader#read` — Pulls the next element, or returns `sentinel` if the reader is closed and empty. This is the fundamental operation: call it repeatedly to consume all elements until it returns your sentinel value:
|
|
363
|
-
|
|
364
|
-
```scala
|
|
365
|
-
abstract class Reader[+Elem] {
|
|
366
|
-
def read[A >: Elem](sentinel: A): A
|
|
367
|
-
}
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
The sentinel value is caller-chosen and should never appear as a real element. For reference types, `null` is convenient. For primitives, use a value outside the domain (e.g., `-1` for unsigned bytes, `Long.MinValue` for `Int`):
|
|
371
|
-
|
|
372
|
-
```scala
|
|
373
|
-
import zio.blocks.streams.io.Reader
|
|
374
|
-
import zio.blocks.chunk.Chunk
|
|
375
|
-
|
|
376
|
-
val r = Reader.fromChunk(Chunk(10, 20))
|
|
377
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$FromChunkInt@677263ef
|
|
378
|
-
val v1 = r.read(-1) // 10
|
|
379
|
-
// v1: Int = 10
|
|
380
|
-
val v2 = r.read(-1) // 20
|
|
381
|
-
// v2: Int = 20
|
|
382
|
-
val v3 = r.read(-1) // -1 (sentinel, reader is closed)
|
|
383
|
-
// v3: Int = -1
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
### Primitive Specialization
|
|
387
|
-
|
|
388
|
-
For primitive types, specialized methods avoid boxing by widening the return type.
|
|
389
|
-
|
|
390
|
-
`Reader#readInt` — Sentinel-return `Int` pull. Returns the element widened to `Long`, or `sentinel` when closed. The sentinel must lie outside `[Int.MinValue, Int.MaxValue]` (typically `Long.MinValue`):
|
|
391
|
-
|
|
392
|
-
```scala
|
|
393
|
-
abstract class Reader[+Elem] {
|
|
394
|
-
def readInt(sentinel: Long)(using Elem <:< Int): Long
|
|
395
|
-
}
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
Why widen to `Long`? If `Reader#readInt` returned `Int`, you couldn't distinguish a real element from the sentinel—both would fit in the int range. By widening to `Long`, the sentinel (e.g., `Long.MinValue`) lies outside the possible int domain, allowing reliable end-of-stream detection. Cast the result back to `Int` if needed: `r.readInt(Long.MinValue).toInt`.
|
|
399
|
-
|
|
400
|
-
`Reader#readLong` — Sentinel-return `Long` pull. Returns the element, or `sentinel` when closed. The sentinel must be a value that never appears in the stream (typically `Long.MaxValue`):
|
|
401
|
-
|
|
402
|
-
```scala
|
|
403
|
-
abstract class Reader[+Elem] {
|
|
404
|
-
def readLong(sentinel: Long)(using Elem <:< Long): Long
|
|
405
|
-
}
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
:::note[Sentinel Collisions Are Disambiguated]
|
|
409
|
-
Unlike `Reader#readInt` which widens to `Long`, `Reader#readLong` has no wider type to safely house the sentinel — a real `Long.MaxValue` element and end-of-stream both come back as the sentinel value. To disambiguate, every read records an out-of-band flag, exposed as `Reader#lastReadWasEOF`: after a read that returned the sentinel, `lastReadWasEOF` is `true` only for genuine end-of-stream. The library's own drain loops test `v == sentinel && reader.lastReadWasEOF`, so streams containing the sentinel value are processed losslessly; manual pull loops should do the same.
|
|
410
|
-
|
|
411
|
-
**Performance Tradeoff:** `Reader#readLong` avoids boxing on every read—the long stays unboxed in memory, and retrieval is a simple memory fetch. In contrast, `Reader#read[Long](sentinel)` boxes each long into a generic `Any` reference, forcing allocation and garbage collection pressure in hot loops. For latency-sensitive or high-throughput workloads (millions of elements per second), this difference is measurable. The `lastReadWasEOF` check costs nothing on the hot path — it only needs consulting on the rare value/sentinel collision.
|
|
412
|
-
:::
|
|
413
|
-
|
|
414
|
-
`Reader#readFloat` — Sentinel-return `Float` pull. Returns the element widened to `Double`, or `sentinel` when closed:
|
|
415
|
-
|
|
416
|
-
```scala
|
|
417
|
-
abstract class Reader[+Elem] {
|
|
418
|
-
def readFloat(sentinel: Double)(using Elem <:< Float): Double
|
|
419
|
-
}
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
Like `Reader#readInt`, widening to `Double` allows the sentinel to lie safely outside the float domain. A float value will always fit in the lower precision bits of the double result, and the sentinel (typically `Double.MaxValue`) occupies the upper range. This ensures you can reliably distinguish real float elements from end-of-stream. Cast back to `Float` if needed: `r.readFloat(Double.MaxValue).toFloat`.
|
|
423
|
-
|
|
424
|
-
`Reader#readDouble` — Sentinel-return `Double` pull. Returns the element, or `sentinel` when closed. The sentinel must be a value outside the domain (typically `Double.MaxValue`):
|
|
425
|
-
|
|
426
|
-
```scala
|
|
427
|
-
abstract class Reader[+Elem] {
|
|
428
|
-
def readDouble(sentinel: Double)(using Elem <:< Double): Double
|
|
429
|
-
}
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
:::danger[Sentinel Collision Risk for Doubles]
|
|
433
|
-
Like `Reader#readLong`, `Reader#readDouble` has no wider type to safely contain the sentinel. If your actual data stream contains `Double.MaxValue` or the sentinel you chose, you will incorrectly detect end-of-stream mid-stream. Always verify that your data domain excludes the chosen sentinel value. Alternatively, use `Reader#read[Double](sentinel)` (the generic method) if you need the flexibility to choose any sentinel regardless of your data—this trades performance (boxing on every read) for safety.
|
|
434
|
-
:::
|
|
435
|
-
|
|
436
|
-
These specialized methods are the hot path for primitive streams — they avoid allocation and boxing entirely:
|
|
437
|
-
|
|
438
|
-
```scala
|
|
439
|
-
import zio.blocks.streams.io.Reader
|
|
440
|
-
import zio.blocks.chunk.Chunk
|
|
441
|
-
|
|
442
|
-
val r = Reader.fromChunk(Chunk(10, 20, 30))
|
|
443
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$FromChunkInt@f89374e
|
|
444
|
-
val sentinel = Long.MinValue
|
|
445
|
-
// sentinel: Long = -9223372036854775808L
|
|
446
|
-
|
|
447
|
-
val v = r.readInt(sentinel)
|
|
448
|
-
// v: Long = 10L
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
### Byte-Level Reading
|
|
452
|
-
|
|
453
|
-
`Reader#readByte` — Reads a single byte (0–255), widened to `Int`. Returns `-1` when the reader is closed. Dispatches on `Reader#jvmType` for zero-boxing when the reader is specialized:
|
|
454
|
-
|
|
455
|
-
```scala
|
|
456
|
-
abstract class Reader[+Elem] {
|
|
457
|
-
def readByte(): Int
|
|
458
|
-
}
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
Read bytes one at a time from a reader until end-of-stream:
|
|
462
|
-
|
|
463
|
-
```scala
|
|
464
|
-
import zio.blocks.streams.io.Reader
|
|
465
|
-
import java.io.ByteArrayInputStream
|
|
466
|
-
|
|
467
|
-
val bytes = Array[Byte](72, 101, 108, 108, 111) // Hello in ASCII bytes
|
|
468
|
-
// bytes: Array[Byte] = Array(72, 101, 108, 108, 111)
|
|
469
|
-
val is = new ByteArrayInputStream(bytes)
|
|
470
|
-
// is: ByteArrayInputStream = java.io.ByteArrayInputStream@78144ba7
|
|
471
|
-
val r = Reader.fromInputStream(is)
|
|
472
|
-
// r: Reader[Byte] = zio.blocks.streams.io.Reader$InputStreamReader@aafbf86
|
|
473
|
-
|
|
474
|
-
def drainBytes(): Unit = {
|
|
475
|
-
val b = r.readByte()
|
|
476
|
-
if (b != -1) {
|
|
477
|
-
println(s"Byte: $b (${b.toChar})")
|
|
478
|
-
drainBytes()
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
drainBytes()
|
|
482
|
-
// Byte: 72 (H)
|
|
483
|
-
// Byte: 101 (e)
|
|
484
|
-
// Byte: 108 (l)
|
|
485
|
-
// Byte: 108 (l)
|
|
486
|
-
// Byte: 111 (o)
|
|
487
|
-
// Output:
|
|
488
|
-
// Byte: 72 (H)
|
|
489
|
-
// Byte: 101 (e)
|
|
490
|
-
// Byte: 108 (l)
|
|
491
|
-
// Byte: 108 (l)
|
|
492
|
-
// Byte: 111 (o)
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
`Reader#readBytes` — Bulk byte read into a caller-supplied buffer, mirroring `java.io.InputStream#read(byte[], int, int)`. The behavior is:
|
|
496
|
-
|
|
497
|
-
- Blocks until at least 1 byte is available.
|
|
498
|
-
- Returns the number of bytes read (`1 <= r <= len`).
|
|
499
|
-
- Returns `-1` when closed and empty.
|
|
500
|
-
- Returns `0` immediately when `len == 0`.
|
|
501
|
-
|
|
502
|
-
The method signature is:
|
|
503
|
-
|
|
504
|
-
```scala
|
|
505
|
-
abstract class Reader[+Elem] {
|
|
506
|
-
def readBytes(buf: Array[Byte], offset: Int, len: Int): Int
|
|
507
|
-
}
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
Read multiple bytes into a buffer in bulk with a loop pattern:
|
|
511
|
-
|
|
512
|
-
```scala
|
|
513
|
-
import zio.blocks.streams.io.Reader
|
|
514
|
-
import java.io.ByteArrayInputStream
|
|
515
|
-
|
|
516
|
-
val bytes = Array[Byte](72, 101, 108, 108, 111) // The word Hello
|
|
517
|
-
// bytes: Array[Byte] = Array(72, 101, 108, 108, 111)
|
|
518
|
-
val is = new ByteArrayInputStream(bytes)
|
|
519
|
-
// is: ByteArrayInputStream = java.io.ByteArrayInputStream@1ba77670
|
|
520
|
-
val r = Reader.fromInputStream(is)
|
|
521
|
-
// r: Reader[Byte] = zio.blocks.streams.io.Reader$InputStreamReader@144c778
|
|
522
|
-
|
|
523
|
-
val buffer = new Array[Byte](3)
|
|
524
|
-
// buffer: Array[Byte] = Array(108, 111, 108)
|
|
525
|
-
|
|
526
|
-
def drainBulk(): Unit = {
|
|
527
|
-
val bytesRead = r.readBytes(buffer, 0, 3)
|
|
528
|
-
if (bytesRead > 0) {
|
|
529
|
-
val chunk = buffer.take(bytesRead).map(_.toChar).mkString
|
|
530
|
-
println(s"Read $bytesRead bytes: $chunk")
|
|
531
|
-
drainBulk()
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
drainBulk()
|
|
535
|
-
// Read 3 bytes: Hel
|
|
536
|
-
// Read 2 bytes: lo
|
|
537
|
-
// Output:
|
|
538
|
-
// Read 3 bytes: Hel
|
|
539
|
-
// Read 2 bytes: lo
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
### Character and Numeric Specialization
|
|
543
|
-
|
|
544
|
-
`Reader#readChar` — Sentinel-return `Char` pull. Returns the element widened to `Int`, or `sentinel` when closed. Requires evidence that `Elem <:< Char`:
|
|
545
|
-
|
|
546
|
-
```scala
|
|
547
|
-
abstract class Reader[+Elem] {
|
|
548
|
-
def readChar(sentinel: Int)(using Elem <:< Char): Int
|
|
549
|
-
}
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
`Reader#readShort` — Sentinel-return `Short` pull. Returns the element widened to `Int`, or `sentinel` when closed:
|
|
553
|
-
|
|
554
|
-
```scala
|
|
555
|
-
abstract class Reader[+Elem] {
|
|
556
|
-
def readShort(sentinel: Int)(using Elem <:< Short): Int
|
|
557
|
-
}
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
`Reader#readBoolean` — Sentinel-return `Boolean` pull. Returns `1` for `true`, `0` for `false`, or `sentinel` when closed. The sentinel must lie outside `[0, 1]` (typically `-1`):
|
|
561
|
-
|
|
562
|
-
```scala
|
|
563
|
-
abstract class Reader[+Elem] {
|
|
564
|
-
def readBoolean(sentinel: Int)(using Elem <:< Boolean): Int
|
|
565
|
-
}
|
|
566
|
-
```
|
|
567
|
-
|
|
568
|
-
### Bulk Operations
|
|
569
|
-
|
|
570
|
-
`Reader#readAll` — Drains the entire reader into a `Chunk`. Dispatches on `Reader#jvmType` for zero-boxing on primitive readers:
|
|
571
|
-
|
|
572
|
-
```scala
|
|
573
|
-
abstract class Reader[+Elem] {
|
|
574
|
-
def readAll[A >: Elem](): Chunk[A]
|
|
575
|
-
}
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
The result is a new chunk containing all remaining elements:
|
|
579
|
-
|
|
580
|
-
```scala
|
|
581
|
-
import zio.blocks.streams.io.Reader
|
|
582
|
-
import zio.blocks.chunk.Chunk
|
|
583
|
-
|
|
584
|
-
val r = Reader.fromChunk(Chunk(10, 20, 30))
|
|
585
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$FromChunkInt@22ee08e9
|
|
586
|
-
val all = r.readAll()
|
|
587
|
-
// all: Chunk[Int] = IndexedSeq(10, 20, 30)
|
|
588
|
-
println(all) // Chunk(10, 20, 30)
|
|
589
|
-
// Chunk(10,20,30)
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
`Reader#skip` — Eagerly discards the first `n` elements. Dispatches on `Reader#jvmType` for zero-boxing when possible:
|
|
593
|
-
|
|
594
|
-
```scala
|
|
595
|
-
abstract class Reader[+Elem] {
|
|
596
|
-
def skip(n: Long): Unit
|
|
597
|
-
}
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
### State Queries
|
|
601
|
-
|
|
602
|
-
`Reader#isClosed` — Returns `true` if the reader is closed. Monotone: once `true`, never returns `false`:
|
|
603
|
-
|
|
604
|
-
```scala
|
|
605
|
-
abstract class Reader[+Elem] {
|
|
606
|
-
def isClosed: Boolean
|
|
607
|
-
}
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
`Reader#readable` — Returns `true` if the next `read()` would return a value (not the sentinel). Default implementation returns `!isClosed`. Buffered readers can override `readable()` for accuracy to peek ahead without consuming:
|
|
611
|
-
|
|
612
|
-
```scala
|
|
613
|
-
abstract class Reader[+Elem] {
|
|
614
|
-
def readable(): Boolean
|
|
615
|
-
}
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
Use `readable()` to check if elements are available before calling `read()`:
|
|
619
|
-
|
|
620
|
-
```scala
|
|
621
|
-
import zio.blocks.streams.io.Reader
|
|
622
|
-
import zio.blocks.chunk.Chunk
|
|
623
|
-
|
|
624
|
-
val r = Reader.fromChunk(Chunk(1, 2))
|
|
625
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$FromChunkInt@65ea641
|
|
626
|
-
println(r.readable()) // true
|
|
627
|
-
// true
|
|
628
|
-
r.read(-1)
|
|
629
|
-
// res32: Int = 1
|
|
630
|
-
println(r.readable()) // true
|
|
631
|
-
// true
|
|
632
|
-
r.read(-1)
|
|
633
|
-
// res34: Int = 2
|
|
634
|
-
println(r.readable()) // false
|
|
635
|
-
// false
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
## Composition
|
|
639
|
-
|
|
640
|
-
Combine multiple readers to build more complex sources:
|
|
641
|
-
|
|
642
|
-
### Concatenation
|
|
643
|
-
|
|
644
|
-
`Reader#concat` — Concatenates this reader with `next`. When this reader is exhausted, it is closed and elements are pulled from `next` (evaluated lazily). Optimized for left-associative chains:
|
|
645
|
-
|
|
646
|
-
```scala
|
|
647
|
-
abstract class Reader[+Elem] {
|
|
648
|
-
def concat[Elem2 >: Elem](next: () => Reader[Elem2]): Reader[Elem2]
|
|
649
|
-
}
|
|
650
|
-
```
|
|
651
|
-
|
|
652
|
-
`Reader#++` — Alias for `Reader#concat`. Syntactic sugar for composing readers:
|
|
653
|
-
|
|
654
|
-
```scala
|
|
655
|
-
abstract class Reader[+Elem] {
|
|
656
|
-
def ++[Elem2 >: Elem](next: => Reader[Elem2]): Reader[Elem2]
|
|
657
|
-
}
|
|
658
|
-
```
|
|
659
|
-
|
|
660
|
-
Here is how concatenation chains multiple readers together:
|
|
661
|
-
|
|
662
|
-
```scala
|
|
663
|
-
import zio.blocks.streams.io.Reader
|
|
664
|
-
import zio.blocks.chunk.Chunk
|
|
665
|
-
|
|
666
|
-
val r1 = Reader.fromChunk(Chunk(1, 2))
|
|
667
|
-
// r1: Reader[Int] = zio.blocks.streams.io.Reader$FromChunkInt@2f0f9a51
|
|
668
|
-
val r2 = Reader.fromChunk(Chunk(3, 4))
|
|
669
|
-
// r2: Reader[Int] = zio.blocks.streams.io.Reader$FromChunkInt@4a811b20
|
|
670
|
-
val combined = r1 ++ r2
|
|
671
|
-
// combined: Reader[Int] = zio.blocks.streams.io.Reader$ConcatReader@1eb0ee36
|
|
672
|
-
|
|
673
|
-
def drain(): Unit = {
|
|
674
|
-
val v = combined.read(-1)
|
|
675
|
-
if (v != -1) {
|
|
676
|
-
println(v)
|
|
677
|
-
drain()
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
drain()
|
|
681
|
-
// 1
|
|
682
|
-
// 2
|
|
683
|
-
// 3
|
|
684
|
-
// 4
|
|
685
|
-
// Output: 1, 2, 3, 4
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
**Optimization**: If this reader is already a `ConcatReader`, the thunk is appended to its internal array and `this` is returned (mutable append, O(1) amortized). Otherwise a new `ConcatReader` is created. This ensures that left-associative chains like `a ++ b ++ c ++ d` compile into a single flat `ConcatReader` with O(1) per-element read, rather than O(n) nested wrappers.
|
|
689
|
-
|
|
690
|
-
## Resource Management
|
|
691
|
-
|
|
692
|
-
Close readers and attach cleanup callbacks:
|
|
693
|
-
|
|
694
|
-
### Closing
|
|
695
|
-
|
|
696
|
-
`Reader#close` — Signals end-of-stream from the consumer side and releases any held resources. Implementations set internal closed state and wake any blocked readers. This is always called in a `finally` block by sinks to guarantee resource cleanup:
|
|
697
|
-
|
|
698
|
-
```scala
|
|
699
|
-
abstract class Reader[+Elem] {
|
|
700
|
-
def close(): Unit
|
|
701
|
-
}
|
|
702
|
-
```
|
|
703
|
-
|
|
704
|
-
`Reader#withRelease` — Wraps this reader so that `release` runs after `Reader#close()`. Useful for attaching cleanup logic:
|
|
705
|
-
|
|
706
|
-
```scala
|
|
707
|
-
abstract class Reader[+Elem] {
|
|
708
|
-
def withRelease(release: () => Unit): Reader[Elem]
|
|
709
|
-
}
|
|
710
|
-
```
|
|
711
|
-
|
|
712
|
-
Here is how cleanup logic is attached to a reader:
|
|
713
|
-
|
|
714
|
-
```scala
|
|
715
|
-
import zio.blocks.streams.io.Reader
|
|
716
|
-
import zio.blocks.chunk.Chunk
|
|
717
|
-
import scala.sys.Prop
|
|
718
|
-
|
|
719
|
-
val cleanupRef = scala.collection.mutable.ListBuffer[String]()
|
|
720
|
-
// cleanupRef: ListBuffer[String] = ListBuffer("cleaned")
|
|
721
|
-
val r = Reader.fromChunk(Chunk(1, 2)).withRelease { () =>
|
|
722
|
-
cleanupRef += "cleaned"
|
|
723
|
-
println("Cleaned up")
|
|
724
|
-
}
|
|
725
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$$anon$1@697b73b8
|
|
726
|
-
|
|
727
|
-
r.close()
|
|
728
|
-
// Cleaned up
|
|
729
|
-
println(cleanupRef.nonEmpty) // true
|
|
730
|
-
// true
|
|
731
|
-
```
|
|
732
|
-
|
|
733
|
-
## Pushdown Operations
|
|
734
|
-
|
|
735
|
-
Readers can sometimes handle skip, limit, and repeat operations natively (O(1), zero per-element cost). These methods attempt that; if the reader cannot handle it natively, they return `false` and the caller must wrap the reader.
|
|
736
|
-
|
|
737
|
-
`Reader#setSkip` — Attempts to set a skip (drop) on this reader. Returns `true` if handled natively, `false` if the caller must wrap. When `true`, the next n elements are discarded before producing. After `Reader#reset()`, the skip is re-applied:
|
|
738
|
-
|
|
739
|
-
```scala
|
|
740
|
-
abstract class Reader[+Elem] {
|
|
741
|
-
def setSkip(n: Long): Boolean
|
|
742
|
-
}
|
|
743
|
-
```
|
|
744
|
-
|
|
745
|
-
Set a skip to discard the first two elements:
|
|
746
|
-
|
|
747
|
-
```scala
|
|
748
|
-
import zio.blocks.streams.io.Reader
|
|
749
|
-
import zio.blocks.chunk.Chunk
|
|
750
|
-
|
|
751
|
-
val r = Reader.fromChunk(Chunk(1, 2, 3, 4, 5))
|
|
752
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$FromChunkInt@377387c0
|
|
753
|
-
val handled = r.setSkip(2)
|
|
754
|
-
// handled: Boolean = true
|
|
755
|
-
println(s"Skip handled natively: $handled")
|
|
756
|
-
// Skip handled natively: true
|
|
757
|
-
|
|
758
|
-
def drain(): Unit = {
|
|
759
|
-
val v = r.read(-1)
|
|
760
|
-
if (v != -1) {
|
|
761
|
-
println(v)
|
|
762
|
-
drain()
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
drain()
|
|
766
|
-
// 3
|
|
767
|
-
// 4
|
|
768
|
-
// 5
|
|
769
|
-
// Output:
|
|
770
|
-
// Skip handled natively: true
|
|
771
|
-
// 3
|
|
772
|
-
// 4
|
|
773
|
-
// 5
|
|
774
|
-
```
|
|
775
|
-
|
|
776
|
-
`Reader#setLimit` — Attempts to set a limit on this reader so it produces at most `n` elements. Returns `true` if handled natively, `false` if the caller must wrap. After `reset()`, the limit is re-applied from the new start position:
|
|
777
|
-
|
|
778
|
-
```scala
|
|
779
|
-
abstract class Reader[+Elem] {
|
|
780
|
-
def setLimit(n: Long): Boolean
|
|
781
|
-
}
|
|
782
|
-
```
|
|
783
|
-
|
|
784
|
-
Set a limit to produce only three elements:
|
|
785
|
-
|
|
786
|
-
```scala
|
|
787
|
-
import zio.blocks.streams.io.Reader
|
|
788
|
-
import zio.blocks.chunk.Chunk
|
|
789
|
-
|
|
790
|
-
val r = Reader.fromChunk(Chunk(1, 2, 3, 4, 5))
|
|
791
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$FromChunkInt@2e287808
|
|
792
|
-
val handled = r.setLimit(3)
|
|
793
|
-
// handled: Boolean = true
|
|
794
|
-
println(s"Limit handled natively: $handled")
|
|
795
|
-
// Limit handled natively: true
|
|
796
|
-
|
|
797
|
-
def drain(): Unit = {
|
|
798
|
-
val v = r.read(-1)
|
|
799
|
-
if (v != -1) {
|
|
800
|
-
println(v)
|
|
801
|
-
drain()
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
drain()
|
|
805
|
-
// 1
|
|
806
|
-
// 2
|
|
807
|
-
// 3
|
|
808
|
-
// Output:
|
|
809
|
-
// Limit handled natively: true
|
|
810
|
-
// 1
|
|
811
|
-
// 2
|
|
812
|
-
// 3
|
|
813
|
-
```
|
|
814
|
-
|
|
815
|
-
`Reader#setRepeat` — Attempts to set this reader into repeat-forever mode, so it restarts from the beginning whenever it would otherwise close. Returns `true` if handled natively, `false` if the caller must wrap:
|
|
816
|
-
|
|
817
|
-
```scala
|
|
818
|
-
abstract class Reader[+Elem] {
|
|
819
|
-
def setRepeat(): Boolean
|
|
820
|
-
}
|
|
821
|
-
```
|
|
822
|
-
|
|
823
|
-
Set repeat mode to emit elements multiple times:
|
|
824
|
-
|
|
825
|
-
```scala
|
|
826
|
-
import zio.blocks.streams.io.Reader
|
|
827
|
-
import zio.blocks.chunk.Chunk
|
|
828
|
-
|
|
829
|
-
val r = Reader.fromChunk(Chunk(1, 2))
|
|
830
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$FromChunkInt@742ce9f
|
|
831
|
-
val handled = r.setRepeat()
|
|
832
|
-
// handled: Boolean = false
|
|
833
|
-
println(s"Repeat handled natively: $handled")
|
|
834
|
-
// Repeat handled natively: false
|
|
835
|
-
|
|
836
|
-
def drain(count: Int): Unit = {
|
|
837
|
-
if (count < 6) {
|
|
838
|
-
val v = r.read(-1)
|
|
839
|
-
println(v)
|
|
840
|
-
drain(count + 1)
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
drain(0)
|
|
844
|
-
// 1
|
|
845
|
-
// 2
|
|
846
|
-
// -1
|
|
847
|
-
// -1
|
|
848
|
-
// -1
|
|
849
|
-
// -1
|
|
850
|
-
// Output:
|
|
851
|
-
// Repeat handled natively: true
|
|
852
|
-
// 1
|
|
853
|
-
// 2
|
|
854
|
-
// 1
|
|
855
|
-
// 2
|
|
856
|
-
// 1
|
|
857
|
-
// 2
|
|
858
|
-
```
|
|
859
|
-
|
|
860
|
-
`Reader#reset` — Rewinds this reader to its initial state, as if freshly constructed. After `Reader#reset()`, all elements are available again from the beginning. Not all readers support this; readers backed by one-shot resources (InputStreams, `java.io.Reader`s) throw `UnsupportedOperationException`:
|
|
861
|
-
|
|
862
|
-
```scala
|
|
863
|
-
abstract class Reader[+Elem] {
|
|
864
|
-
def reset(): Unit
|
|
865
|
-
}
|
|
866
|
-
```
|
|
867
|
-
|
|
868
|
-
After rewinding, the reader starts from the beginning:
|
|
869
|
-
|
|
870
|
-
```scala
|
|
871
|
-
import zio.blocks.streams.io.Reader
|
|
872
|
-
import zio.blocks.chunk.Chunk
|
|
873
|
-
|
|
874
|
-
val r = Reader.fromChunk(Chunk(1, 2, 3))
|
|
875
|
-
// r: Reader[Int] = zio.blocks.streams.io.Reader$FromChunkInt@37838f3b
|
|
876
|
-
println(r.read(-1)) // 1
|
|
877
|
-
// 1
|
|
878
|
-
r.reset()
|
|
879
|
-
println(r.read(-1)) // 1 (back to the beginning)
|
|
880
|
-
// 1
|
|
881
|
-
```
|
|
882
|
-
|
|
883
|
-
## Integration with Stream
|
|
884
|
-
|
|
885
|
-
`Reader` is the compilation target of `Stream`. When you call a terminal operation, the stream compiles to a `Reader`, which is then consumed.
|
|
886
|
-
|
|
887
|
-
You can also open a stream for manual element-by-element pulling using `Stream#start`:
|
|
888
|
-
|
|
889
|
-
```scala
|
|
890
|
-
import zio.blocks.streams.*
|
|
891
|
-
import zio.blocks.streams.io.Reader
|
|
892
|
-
import zio.blocks.scope.*
|
|
893
|
-
|
|
894
|
-
Scope.global.scoped { scope =>
|
|
895
|
-
import scope.*
|
|
896
|
-
|
|
897
|
-
val reader: $[Reader[Int]] = Stream.range(1, 6).start(using scope)
|
|
898
|
-
|
|
899
|
-
$(reader) { r =>
|
|
900
|
-
def drain(): Unit = {
|
|
901
|
-
val v = r.read(-1)
|
|
902
|
-
if (v != -1) {
|
|
903
|
-
println(v) // prints 1, 2, 3, 4, 5
|
|
904
|
-
drain()
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
drain()
|
|
908
|
-
}
|
|
909
|
-
// reader is closed automatically when scope exits
|
|
910
|
-
}
|
|
911
|
-
```
|
|
912
|
-
|
|
913
|
-
:::caution
|
|
914
|
-
Avoid holding references to a `Reader` obtained via `Stream#start` outside its `Scope`. The scope guarantees cleanup; escaping the reader defeats that guarantee.
|
|
915
|
-
:::
|
|
916
|
-
|
|
917
|
-
## Integration with Sink
|
|
918
|
-
|
|
919
|
-
`Reader` and `Sink` are dual: `Reader` is the source, `Sink` is the consumer. When you call `stream.run(sink)`, the stream compiles to a `Reader`, and the sink drains it:
|
|
920
|
-
|
|
921
|
-
```scala
|
|
922
|
-
abstract class Sink[+E, -A, +Z] {
|
|
923
|
-
def drain[A2 <: A](reader: Reader[A2]): Either[E, Z]
|
|
924
|
-
}
|
|
925
|
-
```
|
|
926
|
-
|
|
927
|
-
The sink calls `read()` repeatedly until the reader is closed, transforming the sequence of elements into a result of type `Z`.
|
|
928
|
-
|
|
929
|
-
For example, `Sink.collectAll` drains all elements and returns them as a `Chunk`:
|
|
930
|
-
|
|
931
|
-
```scala
|
|
932
|
-
import zio.blocks.streams._
|
|
933
|
-
|
|
934
|
-
val result = Stream.range(1, 10)
|
|
935
|
-
.run(Sink.collectAll[Int])
|
|
936
|
-
// result: Either[Nothing, Chunk[Int]] = Right(
|
|
937
|
-
// IndexedSeq(1, 2, 3, 4, 5, 6, 7, 8, 9)
|
|
938
|
-
// )
|
|
939
|
-
```
|
|
940
|
-
|
|
941
|
-
## Implementation Notes
|
|
942
|
-
|
|
943
|
-
Understand the design choices and mechanisms that power `Reader`:
|
|
944
|
-
|
|
945
|
-
### Sentinel Protocol
|
|
946
|
-
|
|
947
|
-
The `read(sentinel)` method uses a caller-chosen sentinel value to signal end-of-stream. This avoids the allocation and boxing of wrapping results in `Option` or `Either`. The sentinel must be a value that never appears as a real element.
|
|
948
|
-
|
|
949
|
-
For reference types, `null` is the natural sentinel. For primitives, specialized methods widen the return type and use fixed sentinels:
|
|
950
|
-
|
|
951
|
-
| Type | Sentinel | Method | Return Type |
|
|
952
|
-
|--------|--------------|-------------------|-------------|
|
|
953
|
-
| `Int` | `Long.MinValue` | `readInt(sentinel: Long)` | `Long` |
|
|
954
|
-
| `Long` | `Long.MaxValue` | `readLong(sentinel: Long)` | `Long` |
|
|
955
|
-
| `Float` | `Double.MaxValue` | `readFloat(sentinel: Double)` | `Double` |
|
|
956
|
-
| `Double` | `Double.MaxValue` | `readDouble(sentinel: Double)` | `Double` |
|
|
957
|
-
|
|
958
|
-
:::note
|
|
959
|
-
The `Long.MaxValue` and `Double.MaxValue` sentinels coincide with valid data values. To keep specialized paths lossless, every read additionally records an out-of-band `Reader#lastReadWasEOF` flag: a sentinel-valued result means end-of-stream only when the flag is set. Streams containing exactly those values are therefore processed without truncation, at zero cost on the hot path.
|
|
960
|
-
:::
|
|
961
|
-
|
|
962
|
-
### JVM Type Dispatch
|
|
963
|
-
|
|
964
|
-
`Reader` dispatches on `jvmType` to choose between unboxed and boxed pull paths. Subclasses with primitive specialization override `jvmType`:
|
|
965
|
-
|
|
966
|
-
```scala
|
|
967
|
-
abstract class Reader[+Elem] {
|
|
968
|
-
def jvmType: JvmType = JvmType.AnyRef
|
|
969
|
-
}
|
|
970
|
-
```
|
|
971
|
-
|
|
972
|
-
For example, a `Reader[Int]` backed by a `Chunk[Int]` overrides `jvmType` to return `JvmType.Int`. Then, methods like `Reader#readAll` check `Reader#jvmType` and dispatch to the unboxed `Reader#readInt(sentinel: Long)` path instead of boxing.
|
|
973
|
-
|
|
974
|
-
### Thread Safety
|
|
975
|
-
|
|
976
|
-
`Reader` is **not thread-safe**. It is designed for single-threaded, pull-based consumption. Do not share a `Reader` across threads without external synchronization. If you need concurrent consumption, wrap the reader in a thread-safe queue or use a concurrent streaming library.
|
|
977
|
-
|
|
978
|
-
## Running the Examples
|
|
979
|
-
|
|
980
|
-
All code from this guide is available as runnable examples in the `streams-examples` module. Follow these steps to run them:
|
|
981
|
-
|
|
982
|
-
**Step 1** — Clone the repository and navigate to the project:
|
|
983
|
-
|
|
984
|
-
```bash
|
|
985
|
-
git clone https://github.com/zio/zio-blocks.git
|
|
986
|
-
cd zio-blocks
|
|
987
|
-
```
|
|
988
|
-
|
|
989
|
-
**Step 2** — Run individual examples with sbt:
|
|
990
|
-
|
|
991
|
-
### Basic Reader Construction
|
|
992
|
-
|
|
993
|
-
This example demonstrates the most common reader factories: `Reader.fromChunk`, `Reader.fromIterable`, `Reader.fromRange`, and `Reader.single`. Embed the source:
|
|
994
|
-
|
|
995
|
-
```scala title="streams-examples/src/main/scala/reader/ReaderBasicConstructionExample.scala"
|
|
996
|
-
/*
|
|
997
|
-
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
998
|
-
*
|
|
999
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1000
|
-
* you may not use this file except in compliance with the License.
|
|
1001
|
-
* You may obtain a copy of the License at
|
|
1002
|
-
*
|
|
1003
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1004
|
-
*
|
|
1005
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
1006
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1007
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1008
|
-
* See the License for the specific language governing permissions and
|
|
1009
|
-
* limitations under the License.
|
|
1010
|
-
*/
|
|
1011
|
-
|
|
1012
|
-
package reader
|
|
1013
|
-
|
|
1014
|
-
import zio.blocks.chunk.Chunk
|
|
1015
|
-
import zio.blocks.streams.io.Reader
|
|
1016
|
-
|
|
1017
|
-
/**
|
|
1018
|
-
* Demonstrates the most common Reader factories: fromChunk, fromIterable,
|
|
1019
|
-
* fromRange, single, and unfold. Each reader is drained manually with read() to
|
|
1020
|
-
* show how to consume elements.
|
|
1021
|
-
*/
|
|
1022
|
-
object ReaderBasicConstructionExample extends App {
|
|
1023
|
-
|
|
1024
|
-
println("=== Reader.fromChunk ===")
|
|
1025
|
-
val chunkReader = Reader.fromChunk(Chunk(10, 20, 30))
|
|
1026
|
-
var v = chunkReader.read(-1)
|
|
1027
|
-
while (v != -1) {
|
|
1028
|
-
println(s"Read: $v")
|
|
1029
|
-
v = chunkReader.read(-1)
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
println("\n=== Reader.fromRange ===")
|
|
1033
|
-
val rangeReader = Reader.fromRange(1 to 3)
|
|
1034
|
-
v = rangeReader.read(-1)
|
|
1035
|
-
while (v != -1) {
|
|
1036
|
-
println(s"Read: $v")
|
|
1037
|
-
v = rangeReader.read(-1)
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
println("\n=== Reader.fromIterable ===")
|
|
1041
|
-
val listReader = Reader.fromIterable(List("a", "b", "c"))
|
|
1042
|
-
var sv = listReader.read(null: String)
|
|
1043
|
-
while (sv != null) {
|
|
1044
|
-
println(s"Read: $sv")
|
|
1045
|
-
sv = listReader.read(null: String)
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
println("\n=== Reader.single ===")
|
|
1049
|
-
val singleReader = Reader.single(42)
|
|
1050
|
-
println(s"Read: ${singleReader.read(-1)}")
|
|
1051
|
-
println(s"Read again (closed): ${singleReader.read(-1)}")
|
|
1052
|
-
|
|
1053
|
-
println("\n=== Reader.unfold ===")
|
|
1054
|
-
val unfoldReader = Reader.unfold(1) { s =>
|
|
1055
|
-
if (s > 3) None else Some((s * 10, s + 1))
|
|
1056
|
-
}
|
|
1057
|
-
v = unfoldReader.read(-1)
|
|
1058
|
-
while (v != -1) {
|
|
1059
|
-
println(s"Read: $v")
|
|
1060
|
-
v = unfoldReader.read(-1)
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
println("\n=== Reader state ===")
|
|
1064
|
-
val stateReader = Reader.fromChunk(Chunk(5, 6))
|
|
1065
|
-
println(s"readable before: ${stateReader.readable()}")
|
|
1066
|
-
stateReader.read(-1)
|
|
1067
|
-
println(s"readable after one read: ${stateReader.readable()}")
|
|
1068
|
-
stateReader.read(-1)
|
|
1069
|
-
println(s"readable after exhaustion: ${stateReader.readable()}")
|
|
1070
|
-
println(s"isClosed: ${stateReader.isClosed}")
|
|
1071
|
-
}
|
|
1072
|
-
```
|
|
1073
|
-
|
|
1074
|
-
Run it with:
|
|
1075
|
-
|
|
1076
|
-
```bash
|
|
1077
|
-
sbt "streams-examples/runMain reader.ReaderBasicConstructionExample"
|
|
1078
|
-
```
|
|
1079
|
-
|
|
1080
|
-
### Primitive Specialization and Bulk Operations
|
|
1081
|
-
|
|
1082
|
-
This example shows how primitive readers avoid boxing through `Reader#jvmType` dispatch, and demonstrates `Reader#readAll` and `Reader#skip` for bulk operations. Embed the source:
|
|
1083
|
-
|
|
1084
|
-
```scala title="streams-examples/src/main/scala/reader/ReaderPrimitiveSpecializationExample.scala"
|
|
1085
|
-
/*
|
|
1086
|
-
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
1087
|
-
*
|
|
1088
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1089
|
-
* you may not use this file except in compliance with the License.
|
|
1090
|
-
* You may obtain a copy of the License at
|
|
1091
|
-
*
|
|
1092
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1093
|
-
*
|
|
1094
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
1095
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1096
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1097
|
-
* See the License for the specific language governing permissions and
|
|
1098
|
-
* limitations under the License.
|
|
1099
|
-
*/
|
|
1100
|
-
|
|
1101
|
-
package reader
|
|
1102
|
-
|
|
1103
|
-
import zio.blocks.chunk.Chunk
|
|
1104
|
-
import zio.blocks.streams.io.Reader
|
|
1105
|
-
|
|
1106
|
-
/**
|
|
1107
|
-
* Demonstrates primitive specialization in readers. When a Reader is backed by
|
|
1108
|
-
* primitive types (Int, Long, Float, Double), specialized factory methods like
|
|
1109
|
-
* singleInt, singleLong, etc. avoid boxing entirely. This example also shows
|
|
1110
|
-
* readAll for bulk consumption and skip for advancing the reader.
|
|
1111
|
-
*/
|
|
1112
|
-
object ReaderPrimitiveSpecializationExample extends App {
|
|
1113
|
-
|
|
1114
|
-
println("=== singleInt (zero-boxed) ===")
|
|
1115
|
-
val intReader = Reader.singleInt(42)
|
|
1116
|
-
println(s"Read: ${intReader.read(-1)}")
|
|
1117
|
-
|
|
1118
|
-
println("\n=== singleLong (zero-boxed) ===")
|
|
1119
|
-
val longReader = Reader.singleLong(9999999999L)
|
|
1120
|
-
println(s"Read: ${longReader.read(Long.MaxValue)}")
|
|
1121
|
-
|
|
1122
|
-
println("\n=== singleFloat (zero-boxed) ===")
|
|
1123
|
-
val floatReader = Reader.singleFloat(3.14f)
|
|
1124
|
-
println(s"Read: ${floatReader.readFloat(Float.MaxValue)}")
|
|
1125
|
-
|
|
1126
|
-
println("\n=== singleDouble (zero-boxed) ===")
|
|
1127
|
-
val doubleReader = Reader.singleDouble(2.718)
|
|
1128
|
-
println(s"Read: ${doubleReader.read(Double.MaxValue)}")
|
|
1129
|
-
|
|
1130
|
-
println("\n=== readAll: bulk drain to Chunk ===")
|
|
1131
|
-
val bulkReader = Reader.fromChunk(Chunk(1, 2, 3, 4, 5))
|
|
1132
|
-
val allElements = bulkReader.readAll()
|
|
1133
|
-
println(s"All elements: $allElements")
|
|
1134
|
-
|
|
1135
|
-
println("\n=== skip: discard n elements ===")
|
|
1136
|
-
val skipReader = Reader.fromRange(10 to 15)
|
|
1137
|
-
skipReader.skip(2)
|
|
1138
|
-
// Should now read from 12 onward
|
|
1139
|
-
var v = skipReader.read(-1)
|
|
1140
|
-
val remaining = scala.collection.mutable.ArrayBuffer[Int]()
|
|
1141
|
-
while (v != -1) {
|
|
1142
|
-
remaining += v
|
|
1143
|
-
v = skipReader.read(-1)
|
|
1144
|
-
}
|
|
1145
|
-
println(s"After skipping 2: ${remaining.toList}")
|
|
1146
|
-
|
|
1147
|
-
println("\n=== reset: rewind to beginning ===")
|
|
1148
|
-
val resetReader = Reader.fromChunk(Chunk("x", "y", "z"))
|
|
1149
|
-
var elem = resetReader.read(null: String)
|
|
1150
|
-
println(s"First read: $elem")
|
|
1151
|
-
resetReader.reset()
|
|
1152
|
-
elem = resetReader.read(null: String)
|
|
1153
|
-
println(s"After reset: $elem")
|
|
1154
|
-
|
|
1155
|
-
println("\n=== readable: check if elements remain ===")
|
|
1156
|
-
val checkReader = Reader.fromChunk(Chunk(100, 200))
|
|
1157
|
-
println(s"readable before read: ${checkReader.readable()}")
|
|
1158
|
-
checkReader.read(-1)
|
|
1159
|
-
println(s"readable after one read: ${checkReader.readable()}")
|
|
1160
|
-
checkReader.read(-1)
|
|
1161
|
-
println(s"readable after exhaustion: ${checkReader.readable()}")
|
|
1162
|
-
}
|
|
1163
|
-
```
|
|
1164
|
-
|
|
1165
|
-
Run it with:
|
|
1166
|
-
|
|
1167
|
-
```bash
|
|
1168
|
-
sbt "streams-examples/runMain reader.ReaderPrimitiveSpecializationExample"
|
|
1169
|
-
```
|
|
1170
|
-
|
|
1171
|
-
### Composition and Resource Management
|
|
1172
|
-
|
|
1173
|
-
This example demonstrates reader composition with `Reader#++`, resource cleanup with `Reader#withRelease`, and integration with `Stream.start` for manual pulling. Embed the source:
|
|
1174
|
-
|
|
1175
|
-
```scala title="streams-examples/src/main/scala/reader/ReaderCompositionExample.scala"
|
|
1176
|
-
/*
|
|
1177
|
-
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
1178
|
-
*
|
|
1179
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1180
|
-
* you may not use this file except in compliance with the License.
|
|
1181
|
-
* You may obtain a copy of the License at
|
|
1182
|
-
*
|
|
1183
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1184
|
-
*
|
|
1185
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
1186
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1187
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1188
|
-
* See the License for the specific language governing permissions and
|
|
1189
|
-
* limitations under the License.
|
|
1190
|
-
*/
|
|
1191
|
-
|
|
1192
|
-
package reader
|
|
1193
|
-
|
|
1194
|
-
import zio.blocks.chunk.Chunk
|
|
1195
|
-
import zio.blocks.streams.io.Reader
|
|
1196
|
-
import zio.blocks.streams.Stream
|
|
1197
|
-
import zio.blocks.scope.Scope
|
|
1198
|
-
|
|
1199
|
-
/**
|
|
1200
|
-
* Demonstrates reader composition with ++ (concat), resource cleanup with
|
|
1201
|
-
* withRelease, and integration with Stream.start for manual element-by-element
|
|
1202
|
-
* pulling within a Scope.
|
|
1203
|
-
*/
|
|
1204
|
-
object ReaderCompositionExample extends App {
|
|
1205
|
-
|
|
1206
|
-
println("=== concat: ++ operator ===")
|
|
1207
|
-
val r1 = Reader.fromChunk(Chunk(1, 2, 3))
|
|
1208
|
-
val r2 = Reader.fromChunk(Chunk(4, 5, 6))
|
|
1209
|
-
val combined = r1 ++ r2
|
|
1210
|
-
|
|
1211
|
-
var v = combined.read(-1)
|
|
1212
|
-
val allCombined = scala.collection.mutable.ArrayBuffer[Int]()
|
|
1213
|
-
while (v != -1) {
|
|
1214
|
-
allCombined += v
|
|
1215
|
-
v = combined.read(-1)
|
|
1216
|
-
}
|
|
1217
|
-
println(s"Combined result: ${allCombined.toList}")
|
|
1218
|
-
|
|
1219
|
-
println("\n=== Multiple concat: a ++ b ++ c ===")
|
|
1220
|
-
val ra = Reader.fromChunk(Chunk("a"))
|
|
1221
|
-
val rb = Reader.fromChunk(Chunk("b"))
|
|
1222
|
-
val rc = Reader.fromChunk(Chunk("c"))
|
|
1223
|
-
val multi = ra ++ rb ++ rc
|
|
1224
|
-
|
|
1225
|
-
var sv = multi.read(null: String)
|
|
1226
|
-
val result = scala.collection.mutable.ArrayBuffer[String]()
|
|
1227
|
-
while (sv != null) {
|
|
1228
|
-
result += sv
|
|
1229
|
-
sv = multi.read(null: String)
|
|
1230
|
-
}
|
|
1231
|
-
println(s"Multiple concat: ${result.toList}")
|
|
1232
|
-
|
|
1233
|
-
println("\n=== withRelease: cleanup on close ===")
|
|
1234
|
-
var cleanupCalled = false
|
|
1235
|
-
val resourceReader = Reader.fromChunk(Chunk(10, 20)).withRelease { () =>
|
|
1236
|
-
cleanupCalled = true
|
|
1237
|
-
println(" Cleanup executed!")
|
|
1238
|
-
}
|
|
1239
|
-
var res = resourceReader.read(-1)
|
|
1240
|
-
while (res != -1) {
|
|
1241
|
-
res = resourceReader.read(-1)
|
|
1242
|
-
}
|
|
1243
|
-
resourceReader.close()
|
|
1244
|
-
println(s"Cleanup was called: $cleanupCalled")
|
|
1245
|
-
|
|
1246
|
-
println("\n=== Stream.start: manual pull with Scope ===")
|
|
1247
|
-
Scope.global.scoped { scope =>
|
|
1248
|
-
import scope.*
|
|
1249
|
-
|
|
1250
|
-
// Create a stream and open it for manual pulling
|
|
1251
|
-
val reader: scope.$[Reader[Int]] = Stream.range(1, 6).start(using scope)
|
|
1252
|
-
|
|
1253
|
-
$(reader) { r =>
|
|
1254
|
-
var streamV = r.read(-1)
|
|
1255
|
-
val manualResult = scala.collection.mutable.ArrayBuffer[Int]()
|
|
1256
|
-
while (streamV != -1) {
|
|
1257
|
-
manualResult += streamV
|
|
1258
|
-
streamV = r.read(-1)
|
|
1259
|
-
}
|
|
1260
|
-
println(s"Manual stream pull: ${manualResult.toList}")
|
|
1261
|
-
}
|
|
1262
|
-
// reader is automatically closed when scope exits
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
println("\n=== repeat: infinite reader ===")
|
|
1266
|
-
val infiniteReader = Reader.repeat(99)
|
|
1267
|
-
infiniteReader.setRepeat()
|
|
1268
|
-
|
|
1269
|
-
var repeatCount = 0
|
|
1270
|
-
var repV = infiniteReader.read(-1)
|
|
1271
|
-
while (repeatCount < 3) {
|
|
1272
|
-
println(s"Infinite read $repeatCount: $repV")
|
|
1273
|
-
repV = infiniteReader.read(-1)
|
|
1274
|
-
repeatCount += 1
|
|
1275
|
-
}
|
|
1276
|
-
infiniteReader.close()
|
|
1277
|
-
}
|
|
1278
|
-
```
|
|
1279
|
-
|
|
1280
|
-
Run it with:
|
|
1281
|
-
|
|
1282
|
-
```bash
|
|
1283
|
-
sbt "streams-examples/runMain reader.ReaderCompositionExample"
|
|
1284
|
-
```
|