@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,55 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: scala-2-compatibility
|
|
3
|
-
title: "Scala 2 Compatibility Design Note"
|
|
4
|
-
sidebar_label: "Scala 2 Compatibility"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
This document explains the design of Scala 2.13 support for `zio.blocks.streams` and the constraints that shaped it.
|
|
8
|
-
|
|
9
|
-
## Motivation
|
|
10
|
-
|
|
11
|
-
HTTP data types in `zio-blocks` depend on streams. `zio-http` 4 depends on those HTTP data types. Without Scala 2 stream support, `zio-http` cannot offer Scala 2 support. That dependency chain makes Scala 2.13 support for streams a hard requirement, not an optional nicety.
|
|
12
|
-
|
|
13
|
-
## Non-negotiable constraint: Scala 3 performance
|
|
14
|
-
|
|
15
|
-
The streams implementation uses Scala 3 features on hot combinator paths, especially in `Stream` and `Sink` methods that participate in specialization, error-channel elimination, and zero-boxing-friendly code generation. The `inline` keyword on performance-sensitive helpers is not cosmetic; it directly affects what the JVM sees.
|
|
16
|
-
|
|
17
|
-
A Scala 2 compatibility layer is only acceptable if it leaves the Scala 3 hot path structurally unchanged. Concretely, this rules out:
|
|
18
|
-
|
|
19
|
-
- Moving key instance methods out of the Scala 3 class body into a shared trait
|
|
20
|
-
- Removing or weakening `inline` definitions to satisfy Scala 2's lack of that feature
|
|
21
|
-
- Introducing extra trait boundaries that alter the generated Scala 3 bytecode
|
|
22
|
-
|
|
23
|
-
Adapting surface syntax from Scala 3 `using` to Scala 2 `implicit` is fine where it does not touch the hot path. The risk is not the spelling of contextual parameters; the risk is changing the runtime shape of `Stream` and `Sink` under Scala 3.
|
|
24
|
-
|
|
25
|
-
## Rejected approach: version-specific trait extraction
|
|
26
|
-
|
|
27
|
-
An early draft extracted several instance methods into `StreamVersionSpecific` and `SinkVersionSpecific` traits under `scala-2/` and `scala-3/`, with the shared classes extending those traits. The methods moved or routed through these traits included:
|
|
28
|
-
|
|
29
|
-
- `Stream.++`, `Stream.catchAll`, `Stream.catchDefect`, `Stream.concat`
|
|
30
|
-
- `Stream.flatMap`, `Stream.mapError`, `Stream.orElse`, `Stream.&&`
|
|
31
|
-
- `Sink.mapError`
|
|
32
|
-
|
|
33
|
-
This shape localized the syntax differences neatly, but changed the structure of the Scala 3 hot path enough to produce measurable regressions. Benchmarks run with `streams-benchmark` on Scala 3.8.3 and JDK 25:
|
|
34
|
-
|
|
35
|
-
| Benchmark | Baseline (`main`) | Trait-extraction draft | Change |
|
|
36
|
-
|---|---:|---:|---|
|
|
37
|
-
| `StreamPipelineBench.zb_flatMap` | 14924.328 ops/s | 1178.997 ops/s | ~12x regression |
|
|
38
|
-
| `StreamPipelineBench.zb_concat` | 26003.818 ops/s | 22044.497 ops/s | regression |
|
|
39
|
-
| `StreamPipelineBench.zb_filterMap` | 54812.709 ops/s | 50349.471 ops/s | regression |
|
|
40
|
-
|
|
41
|
-
The `flatMap` result is the clearest signal. Dropping from roughly 14.9k ops/s to 1.18k ops/s is not an acceptable tradeoff for any compatibility layer. The approach was rejected.
|
|
42
|
-
|
|
43
|
-
## Chosen approach: full per-version split
|
|
44
|
-
|
|
45
|
-
The implementation uses a full per-version source split. `Stream`, `Sink`, `Reader`, `Writer`, `Pipeline`, and `Interpreter` each have separate implementations under `scala-3/` and `scala-2/` source directories.
|
|
46
|
-
|
|
47
|
-
Under `scala-3/`, the existing method bodies remain exactly as they were before Scala 2 support was added. `inline` helpers stay in place. No hot path is touched.
|
|
48
|
-
|
|
49
|
-
Under `scala-2/`, equivalent semantics are provided using `final val` and `final def` where Scala 3 uses `inline val` and `inline def`. The Scala 2 surface is behaviorally equivalent; the Scala 2 compiler cannot honour `inline` in the same way, but the final modifier prevents virtual dispatch and allows the JIT similar opportunities.
|
|
50
|
-
|
|
51
|
-
## Maintenance notes
|
|
52
|
-
|
|
53
|
-
Any change to the behavior or public API of `Stream`, `Sink`, `Reader`, `Writer`, `Pipeline`, or `Interpreter` must be applied to both the `scala-2/` and `scala-3/` source trees. The split is intentional and permanent; it is not scaffolding to be collapsed later.
|
|
54
|
-
|
|
55
|
-
When making changes that touch hot combinators, re-run `streams-benchmark` and verify that `zb_flatMap`, `zb_concat`, and `zb_filterMap` do not regress relative to the `main` baseline.
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: zero-boxing
|
|
3
|
-
title: "Zero-Boxing Optimization"
|
|
4
|
-
sidebar_label: "Zero-Boxing"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Working with streams of primitives (integers, longs, doubles, booleans) presents a performance challenge in languages with generic types: **boxing**. Without special care, primitive values get wrapped in objects, causing memory waste and slower code. ZIO Blocks Streams eliminates this overhead entirely through a novel runtime type-dispatch system.
|
|
8
|
-
|
|
9
|
-
## The Boxing Problem
|
|
10
|
-
|
|
11
|
-
In Scala, primitive types (`Int`, `Long`, `Double`, `Boolean`) are fundamentally different from their object counterparts (`Integer`, `Long`, `Double`, `Boolean`). When a generic class like `Stream[E, A]` works with primitives, the compiler must box them into objects to satisfy the generic contract:
|
|
12
|
-
|
|
13
|
-
```scala
|
|
14
|
-
// Without optimization, this boxes each Int into an Integer object
|
|
15
|
-
val stream: Stream[Nothing, Int] = Stream(1, 2, 3, 4, 5)
|
|
16
|
-
val doubled = stream.map(_ * 2) // Each Int is boxed → Integer → boxed result
|
|
17
|
-
val result = doubled.runCollect
|
|
18
|
-
// Result: Each element was boxed, unboxed, boxed again — wasteful!
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
**Performance cost:**
|
|
22
|
-
- Extra heap allocations (memory pressure, more GC)
|
|
23
|
-
- Cache misses (objects spread across memory)
|
|
24
|
-
- Slower CPU operations (dereferencing objects instead of primitive registers)
|
|
25
|
-
|
|
26
|
-
For high-throughput data processing, this overhead is unacceptable.
|
|
27
|
-
|
|
28
|
-
## ZIO Blocks Streams' Solution: JvmType Dispatch
|
|
29
|
-
|
|
30
|
-
Instead of using Scala's `@specialized` annotation (which generates separate classes for each primitive type, bloating binaries), ZIO Blocks Streams uses **compile-time type detection + runtime dispatch**. This gives you the speed of specialization without the binary bloat.
|
|
31
|
-
|
|
32
|
-
### How It Works
|
|
33
|
-
|
|
34
|
-
**Step 1: Compile-Time Detection**
|
|
35
|
-
|
|
36
|
-
When you create a stream of primitives, the compiler infers a `JvmType` implicit that identifies the element type:
|
|
37
|
-
|
|
38
|
-
```scala
|
|
39
|
-
val intStream: Stream[Nothing, Int] = Stream(1, 2, 3)
|
|
40
|
-
// Compiler infers: JvmType.Infer[Int]
|
|
41
|
-
// This information travels through the entire pipeline
|
|
42
|
-
|
|
43
|
-
val doubled = intStream.map(_ * 2)
|
|
44
|
-
// JvmType.Int is available to map's implementation
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**Step 2: Runtime Type Dispatch**
|
|
48
|
-
|
|
49
|
-
Each operation (map, filter, scan, etc.) checks the type at runtime and uses the appropriate fast path:
|
|
50
|
-
|
|
51
|
-
```scala
|
|
52
|
-
// Inside Stream#map's implementation
|
|
53
|
-
def map[B](f: A => B)(implicit jvmTypeA: JvmType.Infer[A]): Stream[E, B] = {
|
|
54
|
-
val jt = jvmTypeA.jvmType
|
|
55
|
-
|
|
56
|
-
if (jt eq JvmType.Int) {
|
|
57
|
-
// Fast unboxed path: read raw Int, apply function, write raw Int
|
|
58
|
-
val intValue = reader.readInt(Long.MinValue)
|
|
59
|
-
val result = f(intValue.asInstanceOf[A])
|
|
60
|
-
// result stays unboxed if B is also Int
|
|
61
|
-
} else if (jt eq JvmType.Long) {
|
|
62
|
-
// Fast unboxed path for Long
|
|
63
|
-
val longValue = reader.readLong(Long.MinValue)
|
|
64
|
-
val result = f(longValue.asInstanceOf[A])
|
|
65
|
-
} else {
|
|
66
|
-
// Generic path: works for any type, uses boxing for primitives
|
|
67
|
-
val value = reader.read(EndOfStream)
|
|
68
|
-
val result = f(value)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
**Step 3: Unboxed Accessors**
|
|
74
|
-
|
|
75
|
-
Instead of a single `read()` method that returns boxed `Any` (where boxed means wrapping primitives in object wrappers like `Integer`, `Long`, `Double`), primitives use specialized accessors that operate directly on primitive values:
|
|
76
|
-
|
|
77
|
-
```scala
|
|
78
|
-
trait Reader[A] {
|
|
79
|
-
// Generic: wraps primitives in objects (Integer, Long, etc.)
|
|
80
|
-
def read(onEnd: A): A
|
|
81
|
-
|
|
82
|
-
// Specialized: primitives stay unboxed
|
|
83
|
-
def readInt(onEnd: Long): Int
|
|
84
|
-
def readLong(onEnd: Long): Long
|
|
85
|
-
def readDouble(onEnd: Long): Double
|
|
86
|
-
def readBoolean(onEnd: Long): Boolean
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
The right method is called at runtime based on the detected type, so primitives bypass boxing entirely.
|
|
91
|
-
|
|
92
|
-
## Practical Benefits
|
|
93
|
-
|
|
94
|
-
To understand the real-world impact, consider how boxing accumulates through a pipeline. Compare a hypothetical boxed implementation with ZIO Streams' zero-boxing approach.
|
|
95
|
-
|
|
96
|
-
### Before (Hypothetical Boxed Streams)
|
|
97
|
-
|
|
98
|
-
Without optimization, each operation in a pipeline adds boxing overhead:
|
|
99
|
-
|
|
100
|
-
```scala
|
|
101
|
-
val nums = Stream(1, 2, 3, 4, 5)
|
|
102
|
-
val result = nums
|
|
103
|
-
.map(_ * 2) // boxes each Int → Integer, applies *, unboxes result
|
|
104
|
-
.filter(_ > 5) // boxes again, compares, unboxes
|
|
105
|
-
.map(_ + 1) // boxes, adds, unboxes
|
|
106
|
-
.runCollect
|
|
107
|
-
// 5 elements × 3 operations × boxing overhead = significant waste
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
**Memory profile:** Each element is boxed/unboxed multiple times, creating temporary objects.
|
|
111
|
-
|
|
112
|
-
### With ZIO Streams (Zero-Boxing)
|
|
113
|
-
|
|
114
|
-
With ZIO Streams' zero-boxing optimization, the same pipeline avoids all boxing overhead:
|
|
115
|
-
|
|
116
|
-
```scala
|
|
117
|
-
val nums = Stream(1, 2, 3, 4, 5)
|
|
118
|
-
val result = nums
|
|
119
|
-
.map(_ * 2) // operates on raw Int in CPU registers
|
|
120
|
-
.filter(_ > 5) // compares raw Int directly
|
|
121
|
-
.map(_ + 1) // raw Int arithmetic
|
|
122
|
-
.runCollect
|
|
123
|
-
// Zero boxing: primitives stay in registers and cache
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
**Memory profile:** Same as non-generic code — primitives never leave the stack/registers.
|
|
127
|
-
|
|
128
|
-
## When Zero-Boxing Applies
|
|
129
|
-
|
|
130
|
-
Zero-boxing is **automatic and transparent**. You get it for free when working with primitives:
|
|
131
|
-
|
|
132
|
-
```scala
|
|
133
|
-
import zio.blocks.streams.*
|
|
134
|
-
|
|
135
|
-
// ✓ Zero-boxing: Int, Long, Double, Boolean
|
|
136
|
-
val ints = Stream(1, 2, 3).map(_ * 2)
|
|
137
|
-
val longs = Stream(1L, 2L, 3L).filter(_ > 0L)
|
|
138
|
-
val doubles = Stream(1.5, 2.5, 3.5).map(_ + 1.0)
|
|
139
|
-
val bools = Stream(true, false, true).filter(identity)
|
|
140
|
-
|
|
141
|
-
// ✓ Zero-boxing: case classes with primitives
|
|
142
|
-
case class Point(x: Int, y: Int)
|
|
143
|
-
val points = Stream(Point(1, 2), Point(3, 4))
|
|
144
|
-
.map(p => Point(p.x * 2, p.y * 2))
|
|
145
|
-
|
|
146
|
-
// ✓ Zero-boxing: tuples of primitives
|
|
147
|
-
val pairs = Stream((1, 2), (3, 4))
|
|
148
|
-
.map { case (x, y) => (x + 1, y + 1) }
|
|
149
|
-
|
|
150
|
-
// Works, but may box for non-primitive types
|
|
151
|
-
val strings = Stream("a", "b", "c").map(_.toUpperCase)
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
You don't need to do anything special — the compiler and runtime handle it automatically.
|
|
155
|
-
|
|
156
|
-
## Comparison: @specialized vs JvmType Dispatch
|
|
157
|
-
|
|
158
|
-
ZIO Blocks Streams' approach differs fundamentally from Scala's traditional `@specialized` annotation. Here's how they compare:
|
|
159
|
-
|
|
160
|
-
**Traditional Scala `@specialized` annotation** generates separate specialized classes at compile time:
|
|
161
|
-
|
|
162
|
-
```scala
|
|
163
|
-
@specialized(Int, Long, Double)
|
|
164
|
-
class Stream[+E, +A] { ... }
|
|
165
|
-
// Generates separate classes:
|
|
166
|
-
// - Stream$mcI$sp (specialized for Int)
|
|
167
|
-
// - Stream$mcJ$sp (specialized for Long)
|
|
168
|
-
// - Stream$mcD$sp (specialized for Double)
|
|
169
|
-
// - Stream (generic fallback)
|
|
170
|
-
// Result: Binary size 4-5x larger
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
**ZIO Blocks `JvmType` dispatch** uses runtime type checking in a single class:
|
|
174
|
-
|
|
175
|
-
```scala
|
|
176
|
-
abstract class Stream[+E, +A] {
|
|
177
|
-
def map[B](f: A => B)(implicit jvmType: JvmType.Infer[A]): Stream[E, B] = {
|
|
178
|
-
if (jvmType.jvmType eq JvmType.Int) { /* fast path */ }
|
|
179
|
-
else { /* generic path */ }
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
// Single class, runtime dispatch
|
|
183
|
-
// Result: Binary size normal, zero boxing at runtime
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
| Metric | `@specialized` | JvmType |
|
|
187
|
-
|--------|---|---|
|
|
188
|
-
| **Binary size** | 4-5x larger | Normal |
|
|
189
|
-
| **Bytecode complexity** | High | Moderate |
|
|
190
|
-
| **Runtime dispatch** | None (compile-time) | Type check once per operation |
|
|
191
|
-
| **Flexibility** | Fixed at compile time | Adaptive at runtime |
|
|
192
|
-
| **Primitive support** | Configurable | Int, Long, Double, Boolean |
|
|
193
|
-
| **Generality** | Good for all generics | Specialized for Stream/Sink |
|
|
194
|
-
|
|
195
|
-
## Implementation Architecture
|
|
196
|
-
|
|
197
|
-
Zero-boxing works across ZIO Blocks Streams' three core abstractions:
|
|
198
|
-
|
|
199
|
-
### Stream[E, A]
|
|
200
|
-
|
|
201
|
-
Detects element type via `JvmType.Infer[A]` and dispatches `Reader` accesses:
|
|
202
|
-
|
|
203
|
-
```scala
|
|
204
|
-
import zio.blocks.streams.*
|
|
205
|
-
|
|
206
|
-
val stream: Stream[Nothing, Int] = Stream(1, 2, 3)
|
|
207
|
-
// JvmType.Int is inferred and available to all operations
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
### Sink[E, A, Z]
|
|
211
|
-
|
|
212
|
-
Accepts elements via unboxed `write` methods matched to the detected type:
|
|
213
|
-
|
|
214
|
-
```scala
|
|
215
|
-
import zio.blocks.streams.*
|
|
216
|
-
import zio.blocks.chunk.Chunk
|
|
217
|
-
|
|
218
|
-
val nums = Stream(1, 2, 3)
|
|
219
|
-
val sum = nums.runFold(0)(_ + _)
|
|
220
|
-
// Sink receives unboxed Int values
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### Pipeline[A, B]
|
|
224
|
-
|
|
225
|
-
Transforms elements without boxing when both A and B are primitives:
|
|
226
|
-
|
|
227
|
-
```scala
|
|
228
|
-
import zio.blocks.streams.*
|
|
229
|
-
|
|
230
|
-
val pipe = Pipeline.map[Int, Int](_ * 2)
|
|
231
|
-
// Entire pipeline operates on raw Int
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
## Performance Impact
|
|
235
|
-
|
|
236
|
-
For typical streaming workloads, zero-boxing provides **2-5x throughput improvement** over boxed approaches:
|
|
237
|
-
|
|
238
|
-
- **CPU-bound operations** (map, filter, scan): 3-5x faster
|
|
239
|
-
- **Memory-bound operations** (collect, fold): 2-3x faster
|
|
240
|
-
- **I/O operations** (reading, writing): Minimal impact (I/O latency dominates)
|
|
241
|
-
|
|
242
|
-
The benefit scales with pipeline depth and data volume. Shallow pipelines see modest gains; deep pipelines (>10 operations) over large datasets see dramatic improvements.
|
|
243
|
-
|
|
244
|
-
## When Polymorphism Is Necessary
|
|
245
|
-
|
|
246
|
-
If you need polymorphic behavior (e.g., different handling for different types), use `JvmType` directly:
|
|
247
|
-
|
|
248
|
-
```scala
|
|
249
|
-
import zio.blocks.streams.*
|
|
250
|
-
import zio.blocks.streams.JvmType
|
|
251
|
-
|
|
252
|
-
def processStream[A](stream: Stream[Nothing, A])(implicit jt: JvmType.Infer[A]): Unit = {
|
|
253
|
-
jt.jvmType match {
|
|
254
|
-
case JvmType.Int =>
|
|
255
|
-
println("Processing integers")
|
|
256
|
-
case JvmType.Long =>
|
|
257
|
-
println("Processing longs")
|
|
258
|
-
case _ =>
|
|
259
|
-
println("Processing generic type")
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
This gives you runtime type information while maintaining full zero-boxing performance.
|
|
265
|
-
|
|
266
|
-
## Summary
|
|
267
|
-
|
|
268
|
-
ZIO Blocks Streams achieves **zero-boxing for primitives** through:
|
|
269
|
-
|
|
270
|
-
1. **Compile-time type detection** via `JvmType.Infer[A]` implicits
|
|
271
|
-
2. **Runtime dispatch** that selects specialized fast paths
|
|
272
|
-
3. **Unboxed accessors** that operate on raw primitives
|
|
273
|
-
4. **Transparent optimization** — you write high-level code, the system handles the details
|
|
274
|
-
|
|
275
|
-
The result is **performance parity with hand-written imperative code** while maintaining the expressiveness and safety of functional streams. No binary bloat, no manual specialization annotations, no boxing overhead.
|