@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.
Files changed (150) hide show
  1. package/guides/compile-time-resource-safety-with-scope.md +16 -17
  2. package/guides/getting-started-with-mux.md +1507 -0
  3. package/guides/query-dsl-extending.md +161 -102
  4. package/guides/query-dsl-fluent-builder.md +217 -157
  5. package/guides/query-dsl-reified-optics.md +12 -10
  6. package/guides/query-dsl-sql.md +246 -165
  7. package/guides/telemetry-guide.md +1069 -0
  8. package/guides/zio-schema-migration.md +29 -22
  9. package/index.md +292 -50
  10. package/package.json +1 -1
  11. package/plans/config-follow-up-prs.md +188 -0
  12. package/plans/config-pr-assessment-roadmap.md +310 -0
  13. package/reference/MuxDataFlow.jsx +250 -0
  14. package/reference/async.md +651 -0
  15. package/reference/chunk.md +3533 -308
  16. package/reference/codegen/case-class.md +436 -0
  17. package/reference/codegen/emitter-config.md +383 -0
  18. package/reference/codegen/examples.md +664 -0
  19. package/reference/codegen/field.md +316 -0
  20. package/reference/codegen/index.md +317 -0
  21. package/reference/codegen/scala-emitter.md +392 -0
  22. package/reference/codegen/scala-file.md +276 -0
  23. package/reference/codegen/sealed-trait.md +408 -0
  24. package/reference/codegen/type-definition.md +340 -0
  25. package/reference/codegen/type-ref.md +201 -0
  26. package/reference/combinators.md +347 -117
  27. package/reference/config.md +158 -0
  28. package/reference/context.md +4 -4
  29. package/reference/datastar.md +346 -0
  30. package/reference/docs.md +1461 -345
  31. package/reference/endpoint/auth-type.md +146 -0
  32. package/reference/endpoint/endpoint.md +297 -0
  33. package/reference/endpoint/http-codec.md +249 -0
  34. package/reference/endpoint/index.md +825 -0
  35. package/reference/endpoint/path-codec.md +237 -0
  36. package/reference/endpoint/route-pattern.md +196 -0
  37. package/reference/endpoint/route-tree.md +111 -0
  38. package/reference/endpoint/segment-codec.md +212 -0
  39. package/reference/html.md +1120 -0
  40. package/reference/htmx/attribute-values.md +359 -0
  41. package/reference/htmx/hx-encoding.md +111 -0
  42. package/reference/htmx/hx-params.md +204 -0
  43. package/reference/htmx/hx-swap.md +276 -0
  44. package/reference/htmx/hx-sync.md +251 -0
  45. package/reference/htmx/hx-target.md +314 -0
  46. package/reference/htmx/hx-trigger.md +457 -0
  47. package/reference/htmx/hx-url-update.md +239 -0
  48. package/reference/htmx/index.md +855 -0
  49. package/reference/http-model/index.md +47 -0
  50. package/reference/http-model/model.md +1481 -0
  51. package/reference/http-model/schema.md +747 -0
  52. package/reference/maybe.md +826 -0
  53. package/reference/media-type.md +2 -2
  54. package/reference/mux.mdx +823 -0
  55. package/reference/openapi.md +1351 -0
  56. package/reference/resource-management/defer-handle.md +1 -1
  57. package/reference/resource-management/resource.md +31 -2
  58. package/reference/resource-management/scope.md +28 -12
  59. package/reference/resource-management/wire.md +3 -7
  60. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  61. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  62. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  63. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  64. package/reference/ringbuffer/advanced.mdx +109 -0
  65. package/reference/ringbuffer/index.mdx +145 -0
  66. package/reference/ringbuffer/mpmc.mdx +151 -0
  67. package/reference/ringbuffer/mpsc.mdx +132 -0
  68. package/reference/ringbuffer/spmc.mdx +108 -0
  69. package/reference/ringbuffer/spsc.mdx +344 -0
  70. package/reference/{allows.md → schema/allows.md} +4 -4
  71. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  72. package/reference/{binding.md → schema/binding.md} +2 -3
  73. package/reference/schema/built-in-codecs/avro.md +451 -0
  74. package/reference/schema/built-in-codecs/bson.md +480 -0
  75. package/reference/schema/built-in-codecs/csv.md +564 -0
  76. package/reference/schema/built-in-codecs/index.md +77 -0
  77. package/reference/schema/built-in-codecs/json/index.md +295 -0
  78. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  79. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  80. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  81. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  82. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  83. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  84. package/reference/schema/built-in-codecs/thrift.md +433 -0
  85. package/reference/schema/built-in-codecs/toon.md +1078 -0
  86. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  87. package/reference/schema/built-in-codecs/yaml.md +552 -0
  88. package/reference/{codec.md → schema/codec.md} +10 -10
  89. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
  90. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  91. package/reference/schema/format.md +92 -0
  92. package/reference/schema/index.md +50 -0
  93. package/reference/schema/migration.md +297 -0
  94. package/reference/{modifier.md → schema/modifier.md} +58 -7
  95. package/reference/{optics.md → schema/optics.md} +2 -2
  96. package/reference/{patch.md → schema/patch.md} +1 -1
  97. package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
  98. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  99. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  100. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  101. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  102. package/reference/{schema.md → schema/schema.md} +12 -0
  103. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  104. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  105. package/reference/smithy.md +533 -0
  106. package/reference/sql/db-codec-deriver.md +71 -0
  107. package/reference/sql/db-codec.md +687 -0
  108. package/reference/sql/db-con.md +271 -0
  109. package/reference/sql/db-connection.md +153 -0
  110. package/reference/sql/db-param-writer.md +77 -0
  111. package/reference/sql/db-param.md +66 -0
  112. package/reference/sql/db-result-reader.md +146 -0
  113. package/reference/sql/db-tx.md +82 -0
  114. package/reference/sql/db-value.md +41 -0
  115. package/reference/sql/ddl.md +85 -0
  116. package/reference/sql/frag.md +254 -0
  117. package/reference/sql/index.md +341 -0
  118. package/reference/sql/repo.md +600 -0
  119. package/reference/sql/sql-dialect.md +73 -0
  120. package/reference/sql/sql-logger.md +62 -0
  121. package/reference/sql/sql-name-mapper.md +70 -0
  122. package/reference/sql/table-metadata.md +134 -0
  123. package/reference/sql/table.md +448 -0
  124. package/reference/sql/transactor-zio.md +399 -0
  125. package/reference/sql/transactor.md +353 -0
  126. package/reference/sql-zio.md +112 -0
  127. package/reference/streams/concurrent-operators.md +106 -0
  128. package/reference/streams/index.md +653 -0
  129. package/reference/streams/pipeline.md +718 -0
  130. package/reference/streams/reader.md +1284 -0
  131. package/reference/streams/scala-2-compatibility.md +55 -0
  132. package/reference/streams/sink.md +1426 -0
  133. package/reference/streams/stream.md +2526 -0
  134. package/reference/streams/writer.md +1045 -0
  135. package/reference/streams/zero-boxing.md +275 -0
  136. package/reference/telemetry.md +693 -0
  137. package/reference/typeid.md +5 -19
  138. package/sidebars.js +238 -43
  139. package/reference/formats.md +0 -694
  140. package/reference/http-model.md +0 -1716
  141. package/reference/streams.md +0 -989
  142. package/ringbuffer.md +0 -249
  143. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  144. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  145. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  146. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  147. /package/reference/{registers.md → schema/registers.md} +0 -0
  148. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  149. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  150. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -3,574 +3,3799 @@ id: chunk
3
3
  title: "Chunk"
4
4
  ---
5
5
 
6
- `Chunk[A]` is an immutable, indexed sequence optimized for high-performance operations. Unlike Scala's built-in collections, Chunk is designed for zero-allocation access patterns, efficient concatenation, and unboxed primitive storage.
6
+ `Chunk[A]` is an **immutable, indexed sequence** of elements of type `A`. Unlike `Array`, `Chunk` provides a purely functional interface with optimized performance for high-level operations. It is lazy on expensive operations like repeated concatenation (which use balanced tree structures) while remaining fast on access.
7
7
 
8
- ## Why Chunk?
8
+ `Chunk[A]`:
9
+ - Is purely functional and immutable
10
+ - Provides O(1) typical random access (O(log n) worst-case for tree-structured chunks)
11
+ - Optimizes concatenation using balanced tree structures (Conc-Trees)
12
+ - Automatically specializes primitive types for efficiency without boxing
13
+ - Lazily materializes only when necessary to maintain performance
9
14
 
10
- Chunk addresses several limitations of standard Scala collections:
15
+ Here is the type signature:
11
16
 
12
- | Feature | Chunk | Vector | Array |
13
- |---------|-------|--------|-------|
14
- | Immutable | ✓ | ✓ | ✗ |
15
- | O(1) indexed access | ✓ | ~O(1) | ✓ |
16
- | Unboxed primitives | ✓ | ✗ | ✓ |
17
- | Efficient concatenation | ✓ | ✓ | ✗ |
18
- | Safe functional interface | ✓ | ✓ | ✗ |
19
- | Lazy slicing | ✓ | ✗ | ✗ |
17
+ ```scala
18
+ sealed abstract class Chunk[+A]
19
+ extends IndexedSeq[A]
20
+ with IndexedSeqOps[A, Chunk, Chunk[A]]
21
+ with StrictOptimizedSeqOps[A, Chunk, Chunk[A]]
22
+ with IterableFactoryDefaults[A, Chunk]
23
+ with Serializable
24
+ ```
25
+
26
+ ## Overview
27
+
28
+ `Chunk` represents a chunk of values. The implementation is backed by arrays for small chunks but transitions to lazy concatenation trees (`Chunk.Concat`) when building large chunks through repeated concatenation. This design eliminates the O(n²) behavior of naive list concatenation while remaining efficient for both element access and transformation.
29
+
30
+ `Chunk` has four main internal representations:
31
+
32
+ ```
33
+ ┌─────────────────────────────┐
34
+ │ Chunk[A] │
35
+ └─────────────────────────────┘
36
+ △
37
+ │
38
+ ┌──────────────┬──────┴────────┬──────────────┐
39
+ │ │ │ │
40
+ ┌────▼───┐ ┌──────▼──────┐ ┌────▼──────┐ ┌───▼────┐
41
+ │ Empty │ │ Singleton │ │Array- │ │ Concat │
42
+ │ │ │(one element)│ │Backed │ │(tree) │
43
+ └────────┘ └─────────────┘ └───────────┘ └────────┘
44
+ ```
45
+
46
+ Chunks automatically choose the most efficient representation:
47
+ - **Empty**: singleton instance for zero elements
48
+ - **Singleton**: single element, no array allocation
49
+ - **Array-backed**: standard array for small sequences
50
+ - **Concat tree**: balanced binary tree for composite chunks, enabling O(log n) concatenation depth
51
+
52
+ The implementation is based on [Conc-Trees for Functional and Parallel Programming](http://aleksandar-prokopec.com/resources/docs/lcpc-conc-trees.pdf) by Aleksandar Prokopec and Martin Odersky.
53
+
54
+ ## The Problem
55
+
56
+ When you work with sequences of data in functional programming, you often need to do two things that seem simple on the surface but are surprisingly tricky to do efficiently at the same time: you need to access elements quickly and merge sequences together without wasting time and memory.
57
+
58
+ Consider a practical scenario. You're building a data processing pipeline where you collect results from multiple parallel operations. Each operation produces a sequence of values, and you need to combine all of them into one final sequence. With a traditional array, combining sequences requires allocating a new, larger array and copying every single element from the source arrays into it. That's O(n) work just for the merging step. If you're doing this repeatedly—merging results from 10 operations, then 20, then 100—the copying overhead adds up quickly and becomes the bottleneck in your program.
59
+
60
+ On the other hand, if you use a linked list for efficiency with concatenation, you face a different problem: accessing the millionth element requires traversing through all the previous elements one by one. That's O(n) time for a single lookup. This works fine if you rarely need random access, but in real applications where you're searching, filtering, and transforming sequences, random access happens constantly. It's a painful tradeoff.
61
+
62
+ There's another subtle but important issue: memory overhead. A linked list that holds a million integers wastes significant memory because each node must store a pointer to the next node in addition to the actual value. An array is compact and efficient, but when you concatenate arrays, you're wasting both time (on copying) and memory (on allocating temporary intermediate arrays).
63
+
64
+ The real problem isn't just performance in isolation—it's that conventional sequence types force you to choose between mutually incompatible goals. You can optimize for random access (arrays) or for concatenation (lists), but not both efficiently. In a functional programming world where immutability is a core principle, this creates a tension: we want pure, immutable sequences that behave well in parallel processing scenarios where splitting and merging are fundamental operations. Yet the standard approaches either copy too much data or traverse too slowly.
20
65
 
21
- Key advantages:
66
+ This tension becomes especially acute when you're building systems that process large streams of data, perform data-parallel operations across multiple cores, or need to concatenate sequences as part of their normal operation. Every time you reach for an array and concatenate them, you're paying a hidden tax in copying. Every time you reach for a list, you're paying a hidden tax in traversal. Neither option feels quite right for a modern, functional programming experience.
22
67
 
23
- - **Zero-boxing for primitives**: `Chunk[Int]`, `Chunk[Double]`, etc. store values unboxed in specialized arrays
24
- - **Lazy concatenation**: Uses balanced tree structures (based on Conc-Trees) for O(log n) concatenation
25
- - **Efficient slicing**: `drop`, `take`, and `slice` create views without copying
26
- - **Automatic materialization**: Deep operation chains are materialized when depth exceeds thresholds
27
- - **Scala collections integration**: Implements `IndexedSeq` for seamless interoperability
68
+ ## The Solution
69
+
70
+ Chunk solves this problem by combining the strengths of both arrays and balanced trees into a single, cohesive data structure. The key insight is to use different internal representations for different purposes: arrays for small, simple sequences (where the overhead of tree structures would hurt more than help), and balanced tree structures (specifically, Conc-Trees) for composite sequences built through concatenation and transformation.
71
+
72
+ When you create a small Chunk directly—say, `Chunk(1, 2, 3, 4, 5)`—it's backed by a simple array internally. You get all the benefits: O(1) random access, compact memory, cache-friendly performance. There's no overhead from tree structures or pointers. You're just using an array, exactly as you would expect.
73
+
74
+ But here's where it gets clever. When you concatenate two Chunks, instead of eagerly copying all the data into a new array, Chunk creates a lightweight tree node that simply links the two chunks together. This is nearly free—just a few pointer assignments. If you concatenate again, you're building a small balanced tree. The beauty is that this tree structure guarantees that no matter how many times you concatenate, random access still works in O(log n) time. The tree stays balanced through careful structural invariants, so the depth never grows too much. For practical sequence sizes you'd work with, O(log n) access is nearly as fast as O(1), with much better memory usage.
75
+
76
+ Chunk also includes specialized support for incremental building through the ChunkBuilder API. When you're constructing a sequence by repeatedly appending elements, ChunkBuilder uses a buffering strategy: it accumulates elements in a small array, and only when the array fills up does it append it to the growing Chunk tree. This means appending is effectively O(1) amortized time—comparable to dynamically sized arrays, but without the re-copying overhead when the array grows.
77
+
78
+ There's another important optimization: Chunk is designed to handle primitive types like `Int`, `Long`, `Double`, and `Byte` without boxing them. This is a significant performance win in practice. The specialized constructors and accessors ensure that a `Chunk[Int]` really does store integers directly in memory, not wrapped in Java Integer objects.
79
+
80
+ For functional programming specifically, Chunk embraces immutability completely. When you transform a Chunk, you get a new Chunk. The original remains unchanged. This makes Chunk naturally safe to share across parallel operations without locks or coordination. Different threads can safely read from the same Chunk simultaneously, and transformations produce new, independent Chunks.
81
+
82
+ The practical result is that Chunk gives you a sequence type that's genuinely efficient across all the operations you actually need: random access is fast, concatenation is fast, transformation is efficient, and memory usage stays reasonable. There's no invisible copying happening in the background, and no painful linear-time traversals. You get array-like performance where it matters most, without sacrificing the ability to build and merge sequences efficiently. For functional programmers and anyone working with immutable data structures, Chunk eliminates the artificial tradeoff between performance and purity.
28
83
 
29
84
  ## Installation
30
85
 
31
- Add the following to your `build.sbt`:
86
+ Chunk is available in the core `zio-blocks` library:
32
87
 
33
88
  ```scala
34
- libraryDependencies += "dev.zio" %% "zio-blocks-chunk" % "<version>"
89
+ libraryDependencies += "dev.zio" %% "zio-blocks-chunk" % "0.0.51"
35
90
  ```
36
91
 
37
- For cross-platform projects (Scala.js):
92
+ For Scala.js support:
38
93
 
39
94
  ```scala
40
- libraryDependencies += "dev.zio" %%% "zio-blocks-chunk" % "<version>"
95
+ libraryDependencies += "dev.zio" %%% "zio-blocks-chunk" % "0.0.51"
41
96
  ```
42
97
 
43
- Supported Scala versions: 2.13.x and 3.x
98
+ Supports Scala 2.13.x and 3.x.
99
+
100
+ ## Factory Methods
44
101
 
45
- ## Creating Chunks
102
+ Chunk provides comprehensive factory methods for creating instances from various sources. Each factory is optimized for its data source with different performance and safety characteristics. Choose based on your data source and use case.
46
103
 
47
- ### From Varargs
104
+ ### Direct Construction
105
+
106
+ The simplest ways to create chunks from individual elements or single values:
107
+
108
+ #### `Chunk.apply` — Varargs Constructor
109
+
110
+ Create a chunk from individual elements using varargs syntax:
111
+
112
+ ```scala
113
+ object Chunk {
114
+ def apply[A](as: A*): Chunk[A]
115
+ }
116
+ ```
117
+
118
+ The simplest way to create chunks from individual elements:
48
119
 
49
120
  ```scala
50
121
  import zio.blocks.chunk.Chunk
51
122
 
52
123
  val numbers = Chunk(1, 2, 3, 4, 5)
53
- val strings = Chunk("hello", "world")
54
- val empty = Chunk.empty[Int]
124
+ // numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
125
+
126
+ val strings = Chunk("alice", "bob", "charlie")
127
+ // strings: Chunk[String] = IndexedSeq("alice", "bob", "charlie")
128
+
129
+ val mixed = Chunk(1, "two", 3.0)
130
+ // mixed: Chunk[Int | String | Double] = IndexedSeq(1, "two", 3.0)
131
+ ```
132
+
133
+ **Performance:** O(n) — creates array-backed chunk, optimal for small to medium sizes.
134
+
135
+ #### `Chunk.single` — Single-Element Constructor
136
+
137
+ Create an efficient single-element chunk:
138
+
139
+ ```scala
140
+ object Chunk {
141
+ def single[A](a: A): Chunk[A]
142
+ }
55
143
  ```
56
144
 
57
- ### From a Single Element
145
+ Convenient for wrapping individual values without array overhead:
58
146
 
59
147
  ```scala
60
148
  import zio.blocks.chunk.Chunk
61
149
 
62
- val single = Chunk.single(42)
63
- val unit = Chunk.unit // Chunk(())
150
+ val one = Chunk.single("hello")
151
+ // one: Chunk[String] = IndexedSeq("hello")
152
+
153
+ val singleInt = Chunk.single(42)
154
+ // singleInt: Chunk[Int] = IndexedSeq(42)
64
155
  ```
65
156
 
66
- ### From Arrays
157
+ **Performance:** O(1) — specialized for single-element chunks, no array allocation.
67
158
 
68
- When you have an existing array, use `fromArray`. Note that the array should not be mutated after wrapping:
159
+ #### `Chunk.empty` — Empty Chunk
69
160
 
70
- ```scala
71
- import zio.blocks.chunk.Chunk
161
+ Create an empty chunk (singleton instance):
72
162
 
73
- val arr = Array(1, 2, 3)
74
- val chunk = Chunk.fromArray(arr)
163
+ ```scala
164
+ object Chunk {
165
+ def empty[A]: Chunk[A]
166
+ }
75
167
  ```
76
168
 
77
- ### From Iterables and Iterators
169
+ Returns the shared empty chunk singleton:
78
170
 
79
171
  ```scala
80
172
  import zio.blocks.chunk.Chunk
81
173
 
82
- val fromList = Chunk.fromIterable(List(1, 2, 3))
83
- val fromVector = Chunk.fromIterable(Vector("a", "b"))
84
- val fromIter = Chunk.fromIterator(Iterator.range(0, 10))
174
+ val empty = Chunk.empty[Int]
175
+ // empty: Chunk[Int] = IndexedSeq()
176
+
177
+ empty.length
178
+ // res3: Int = 0
179
+
180
+ empty.isEmpty
181
+ // res4: Boolean = true
182
+ ```
183
+
184
+ **Performance:** O(1) — returns shared singleton, no allocation.
185
+
186
+ ### Generation Methods
187
+
188
+ Create chunks by generating values using functions:
189
+
190
+ #### `Chunk.fill` — Repeat Element N Times
191
+
192
+ Create a chunk by repeating an element n times:
193
+
194
+ ```scala
195
+ object Chunk {
196
+ def fill[A](n: Int)(elem: => A): Chunk[A]
197
+ }
85
198
  ```
86
199
 
87
- ### From Java Collections
200
+ Useful for creating uniform chunks:
88
201
 
89
202
  ```scala
90
203
  import zio.blocks.chunk.Chunk
91
- import java.util
92
204
 
93
- val javaList = new util.ArrayList[String]()
94
- javaList.add("one")
95
- javaList.add("two")
205
+ val repeated = Chunk.fill(5)("x")
206
+ // repeated: Chunk[String] = IndexedSeq("x", "x", "x", "x", "x")
96
207
 
97
- val chunk = Chunk.fromJavaIterable(javaList)
208
+ val zeros = Chunk.fill(3)(0)
209
+ // zeros: Chunk[Int] = IndexedSeq(0, 0, 0)
210
+
211
+ val ones = Chunk.fill(4)(1)
212
+ // ones: Chunk[Int] = IndexedSeq(1, 1, 1, 1)
98
213
  ```
99
214
 
100
- ### From NIO Buffers
215
+ **Performance:** O(n) — creates array-backed chunk of size n.
216
+
217
+ #### `Chunk.iterate` — Repeatedly Apply Function
218
+
219
+ Create a chunk by repeatedly applying a function starting from an initial value:
220
+
221
+ ```scala
222
+ object Chunk {
223
+ def iterate[A](start: A, len: Int)(f: A => A): Chunk[A]
224
+ }
225
+ ```
101
226
 
102
- Chunk provides direct integration with Java NIO buffers:
227
+ Generates sequences by function iteration:
103
228
 
104
229
  ```scala
105
230
  import zio.blocks.chunk.Chunk
106
- import java.nio.ByteBuffer
107
231
 
108
- val buffer = ByteBuffer.wrap(Array[Byte](1, 2, 3, 4))
109
- val bytes = Chunk.fromByteBuffer(buffer)
232
+ val powers = Chunk.iterate(1, 5)(_ * 2)
233
+ // powers: Chunk[Int] = IndexedSeq(1, 2, 4, 8, 16)
234
+
235
+ val alphabet = Chunk.iterate('a', 3)(c => (c + 1).toChar)
236
+ // alphabet: Chunk[Char] = IndexedSeq('a', 'b', 'c')
237
+
238
+ val decreasing = Chunk.iterate(10, 4)(_ - 1)
239
+ // decreasing: Chunk[Int] = IndexedSeq(10, 9, 8, 7)
110
240
  ```
111
241
 
112
- Available buffer constructors:
113
- - `Chunk.fromByteBuffer(ByteBuffer): Chunk[Byte]`
114
- - `Chunk.fromCharBuffer(CharBuffer): Chunk[Char]`
115
- - `Chunk.fromIntBuffer(IntBuffer): Chunk[Int]`
116
- - `Chunk.fromLongBuffer(LongBuffer): Chunk[Long]`
117
- - `Chunk.fromShortBuffer(ShortBuffer): Chunk[Short]`
118
- - `Chunk.fromFloatBuffer(FloatBuffer): Chunk[Float]`
119
- - `Chunk.fromDoubleBuffer(DoubleBuffer): Chunk[Double]`
242
+ **Performance:** O(n) — function applied n times sequentially.
243
+
244
+ #### `Chunk.unfold` — Generate from State
245
+
246
+ Generate a chunk by repeatedly applying a function that returns an optional value:
247
+
248
+ ```scala
249
+ object Chunk {
250
+ def unfold[S, A](s: S)(f: S => Option[(A, S)]): Chunk[A]
251
+ }
252
+ ```
120
253
 
121
- ### Generator Functions
254
+ Powerful for generating chunks from state transitions:
122
255
 
123
256
  ```scala
124
257
  import zio.blocks.chunk.Chunk
125
258
 
126
- val filled = Chunk.fill(5)("x") // Chunk("x", "x", "x", "x", "x")
127
- val iterated = Chunk.iterate(1, 5)(_ * 2) // Chunk(1, 2, 4, 8, 16)
128
- val unfolded = Chunk.unfold(0)(n => if (n < 5) Some((n, n + 1)) else None)
259
+ // Count from 1 to 5
260
+ val counted = Chunk.unfold(1) { n =>
261
+ if (n <= 5) Some((n, n + 1)) else None
262
+ }
263
+ // counted: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
264
+
265
+ // Generate fibonacci-like sequence
266
+ val fibs = Chunk.unfold((1, 1)) { case (a, b) =>
267
+ if (a <= 50) Some((a, (b, a + b))) else None
268
+ }
269
+ // fibs: Chunk[Int] = IndexedSeq(1, 1, 2, 3, 5, 8, 13, 21, 34)
129
270
  ```
130
271
 
131
- ## Core Operations
272
+ **Performance:** O(n) where n is number of generated elements.
132
273
 
133
- ### Element Access
274
+ ### Collection Conversion Methods
275
+
276
+ Create chunks from existing Scala collections:
277
+
278
+ #### `Chunk.fromIterable` — From Scala Collections
279
+
280
+ Convert any Scala iterable into a chunk:
281
+
282
+ ```scala
283
+ object Chunk {
284
+ def fromIterable[A](it: Iterable[A]): Chunk[A]
285
+ }
286
+ ```
287
+
288
+ Supports all Scala collection types (List, Vector, Set, Seq, etc.). For Chunk and Vector, data may be shared; for others, data is copied:
134
289
 
135
290
  ```scala
136
291
  import zio.blocks.chunk.Chunk
137
292
 
138
- val chunk = Chunk(10, 20, 30, 40, 50)
293
+ val list = List(1, 2, 3)
294
+ // list: List[Int] = List(1, 2, 3)
295
+ val chunk = Chunk.fromIterable(list)
296
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
139
297
 
140
- val first = chunk(0) // 10
141
- val second = chunk(1) // 20
142
- val head = chunk.head // 10
143
- val last = chunk.last // 50
144
- val len = chunk.length // 5
298
+ val vector = Vector("x", "y", "z")
299
+ // vector: Vector[String] = Vector("x", "y", "z")
300
+ val chunkFromVec = Chunk.fromIterable(vector)
301
+ // chunkFromVec: Chunk[String] = IndexedSeq("x", "y", "z")
145
302
 
146
- val maybeHead = chunk.headOption // Some(10)
147
- val maybeLast = chunk.lastOption // Some(50)
303
+ val set = Set(10, 20, 30)
304
+ // set: Set[Int] = Set(10, 20, 30)
305
+ val chunkFromSet = Chunk.fromIterable(set)
306
+ // chunkFromSet: Chunk[Int] = IndexedSeq(10, 20, 30)
148
307
  ```
149
308
 
150
- For primitive chunks, specialized accessors avoid boxing:
309
+ **Performance:** O(n) for most types; O(1) for Vector (data sharing).
310
+
311
+ #### `Chunk.from` — Generic from Iterable (Alias)
312
+
313
+ Shorter alias for `fromIterable`:
314
+
315
+ ```scala
316
+ object Chunk {
317
+ def from[A](it: Iterable[A]): Chunk[A]
318
+ }
319
+ ```
320
+
321
+ Provides a concise name for generic iterable conversion:
151
322
 
152
323
  ```scala
153
324
  import zio.blocks.chunk.Chunk
154
325
 
155
- val ints = Chunk(1, 2, 3)
156
- val i: Int = ints.int(0) // unboxed access
326
+ val list = List(1, 2, 3)
327
+ // list: List[Int] = List(1, 2, 3)
328
+ val chunk = Chunk.from(list)
329
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
330
+ ```
157
331
 
158
- val bytes = Chunk[Byte](1, 2, 3)
159
- val b: Byte = bytes.byte(0) // unboxed access
332
+ **Performance:** Same as `fromIterable`.
160
333
 
161
- val doubles = Chunk(1.0, 2.0, 3.0)
162
- val d: Double = doubles.double(0) // unboxed access
334
+ #### `Chunk.fromIterator` — From Iterator
335
+
336
+ Consume an iterator and collect all elements into a chunk:
337
+
338
+ ```scala
339
+ object Chunk {
340
+ def fromIterator[A](iterator: Iterator[A]): Chunk[A]
341
+ }
163
342
  ```
164
343
 
165
- ### Transformations
344
+ Exhausts the iterator and builds a chunk:
166
345
 
167
346
  ```scala
168
347
  import zio.blocks.chunk.Chunk
169
348
 
170
- val chunk = Chunk(1, 2, 3, 4, 5)
349
+ val iter = Iterator(5, 10, 15)
350
+ // iter: Iterator[Int] = empty iterator
351
+ val chunk = Chunk.fromIterator(iter)
352
+ // chunk: Chunk[Int] = IndexedSeq(5, 10, 15)
171
353
 
172
- val doubled = chunk.map(_ * 2) // Chunk(2, 4, 6, 8, 10)
173
- val filtered = chunk.filter(_ > 2) // Chunk(3, 4, 5)
174
- val flatted = chunk.flatMap(n => Chunk(n, n)) // Chunk(1, 1, 2, 2, ...)
175
- val collected = chunk.collect { case n if n % 2 == 0 => n * 10 } // Chunk(20, 40)
354
+ // Iterator is now exhausted
355
+ val isEmpty = iter.hasNext
356
+ // isEmpty: Boolean = false
176
357
  ```
177
358
 
178
- ### Concatenation
359
+ **Performance:** O(n) where n is number of elements in iterator.
360
+
361
+ #### `Chunk.fromArray` — From Array (Zero-Copy)
362
+
363
+ Create a chunk backed by an array without copying:
364
+
365
+ ```scala
366
+ object Chunk {
367
+ def fromArray[A](array: Array[A]): Chunk[A]
368
+ }
369
+ ```
179
370
 
180
- Concatenation is efficient—Chunk uses balanced tree structures to avoid copying:
371
+ **Critical Warning:** This is a zero-copy operation that **holds a direct reference to the provided array**. The array **must not be mutated** after creating the chunk. Mutations become visible through the chunk:
181
372
 
182
373
  ```scala
183
374
  import zio.blocks.chunk.Chunk
184
375
 
185
- val a = Chunk(1, 2, 3)
186
- val b = Chunk(4, 5, 6)
376
+ val arr = Array(10, 20, 30)
377
+ // arr: Array[Int] = Array(10, 20, 30)
378
+ val chunk = Chunk.fromArray(arr)
379
+ // chunk: Chunk[Int] = IndexedSeq(10, 20, 30)
187
380
 
188
- val combined = a ++ b // Chunk(1, 2, 3, 4, 5, 6)
189
- val appended = a :+ 4 // Chunk(1, 2, 3, 4)
190
- val prepended = 0 +: a // Chunk(0, 1, 2, 3)
381
+ chunk
382
+ // res12: Chunk[Int] = IndexedSeq(10, 20, 30)
191
383
  ```
192
384
 
193
- ### Slicing
194
-
195
- Slicing operations create views and don't copy data:
385
+ Mutating the source array breaks immutability:
196
386
 
197
387
  ```scala
198
388
  import zio.blocks.chunk.Chunk
199
389
 
200
- val chunk = Chunk(1, 2, 3, 4, 5, 6, 7, 8)
390
+ val arr = Array(10, 20, 30)
391
+ val chunk = Chunk.fromArray(arr)
392
+ arr(0) = 99 // DANGER: mutation visible through chunk!
393
+ ```
201
394
 
202
- val firstThree = chunk.take(3) // Chunk(1, 2, 3)
203
- val lastThree = chunk.takeRight(3) // Chunk(6, 7, 8)
204
- val dropped = chunk.drop(2) // Chunk(3, 4, 5, 6, 7, 8)
205
- val sliced = chunk.slice(2, 5) // Chunk(3, 4, 5)
395
+ The mutation is visible:
206
396
 
207
- val (left, right) = chunk.splitAt(4) // (Chunk(1,2,3,4), Chunk(5,6,7,8))
397
+ ```scala
398
+ chunk
399
+ // res15: Chunk[Int] = IndexedSeq(99, 20, 30)
208
400
  ```
209
401
 
210
- ### Conditional Operations
402
+ **Safe Alternative:** Use `fromIterable` which copies data and guarantees immutability:
211
403
 
212
404
  ```scala
213
405
  import zio.blocks.chunk.Chunk
214
406
 
215
- val chunk = Chunk(1, 2, 3, 4, 5, 6)
407
+ val arr = Array(10, 20, 30)
408
+ // arr: Array[Int] = Array(10, 20, 30)
409
+ val chunk = Chunk.fromIterable(arr.toList) // Safe copy
410
+ // chunk: Chunk[Int] = IndexedSeq(10, 20, 30)
216
411
 
217
- val takeWhileSmall = chunk.takeWhile(_ < 4) // Chunk(1, 2, 3)
218
- val dropWhileSmall = chunk.dropWhile(_ < 4) // Chunk(4, 5, 6)
219
- val takeUntilBig = chunk.takeWhile(_ <= 3) // Chunk(1, 2, 3)
220
- val dropUntilBig = chunk.dropUntil(_ > 3) // Chunk(5, 6)
412
+ // Now mutations don't affect chunk
221
413
  ```
222
414
 
223
- ### Folding and Reduction
415
+ **Performance:** O(1) zero-copy; O(n) for safe copy alternative.
224
416
 
225
- ```scala
226
- import zio.blocks.chunk.Chunk
417
+ ### Java Interoperability Methods
227
418
 
228
- val chunk = Chunk(1, 2, 3, 4, 5)
419
+ Create chunks from Java collections and buffers:
229
420
 
230
- val sum = chunk.foldLeft(0)(_ + _) // 15
231
- val product = chunk.foldRight(1)(_ * _) // 120
232
- val summed = chunk.reduce(_ + _) // 15
421
+ #### From `java.nio` Buffers
233
422
 
234
- val runningSum = chunk.foldWhile(0)(_ < 10)(_ + _) // 10 (1+2+3+4)
423
+ Create chunks directly from Java NIO buffers (`ByteBuffer`, `CharBuffer`, etc.):
424
+
425
+ ```scala
426
+ object Chunk {
427
+ def fromByteBuffer(buffer: ByteBuffer): Chunk[Byte]
428
+ def fromCharBuffer(buffer: CharBuffer): Chunk[Char]
429
+ def fromIntBuffer(buffer: IntBuffer): Chunk[Int]
430
+ def fromLongBuffer(buffer: LongBuffer): Chunk[Long]
431
+ def fromFloatBuffer(buffer: FloatBuffer): Chunk[Float]
432
+ def fromDoubleBuffer(buffer: DoubleBuffer): Chunk[Double]
433
+ def fromShortBuffer(buffer: ShortBuffer): Chunk[Short]
434
+ }
235
435
  ```
236
436
 
237
- ### Searching and Predicates
437
+ Working with NIO buffers is seamless:
238
438
 
239
439
  ```scala
240
440
  import zio.blocks.chunk.Chunk
441
+ import java.nio.ByteBuffer
241
442
 
242
- val chunk = Chunk(1, 2, 3, 4, 5)
443
+ val buffer = ByteBuffer.wrap(Array[Byte](1, 2, 3))
444
+ // buffer: ByteBuffer = java.nio.HeapByteBuffer[pos=0 lim=3 cap=3]
445
+ val chunk = Chunk.fromByteBuffer(buffer)
446
+ // chunk: Chunk[Byte] = IndexedSeq(1, 2, 3)
447
+ ```
448
+
449
+ **Performance:** O(n) — copies data from buffer into array-backed chunk.
450
+
451
+ #### `Chunk.fromJavaIterable` — From Java Iterable
452
+
453
+ Create a chunk from a Java `Iterable`:
243
454
 
244
- val hasEven = chunk.exists(_ % 2 == 0) // true
245
- val allSmall = chunk.forall(_ < 10) // true
246
- val found = chunk.find(_ > 3) // Some(4)
247
- val index = chunk.indexWhere(_ > 3) // 3
455
+ ```scala
456
+ object Chunk {
457
+ def fromJavaIterable[A](iterable: java.lang.Iterable[A]): Chunk[A]
458
+ }
248
459
  ```
249
460
 
250
- ### Zipping
461
+ Interoperate with Java APIs that produce iterables:
251
462
 
252
463
  ```scala
253
464
  import zio.blocks.chunk.Chunk
465
+ import java.util.Arrays
254
466
 
255
- val as = Chunk("a", "b", "c")
256
- val bs = Chunk(1, 2, 3)
257
-
258
- val zipped = as.zip(bs) // Chunk(("a",1), ("b",2), ("c",3))
259
- val withIndex = as.zipWithIndex // Chunk(("a",0), ("b",1), ("c",2))
260
- val zipWith = as.zipWith(bs)(_ + _) // Chunk("a1", "b2", "c3")
261
- val zipAll = as.zipAll(Chunk(1, 2)) // handles different lengths
467
+ val javaList = Arrays.asList("a", "b", "c")
468
+ // javaList: List[String] = [a, b, c]
469
+ val chunk = Chunk.fromJavaIterable(javaList)
470
+ // chunk: Chunk[String] = IndexedSeq("a", "b", "c")
262
471
  ```
263
472
 
264
- ### Updating Elements
473
+ **Performance:** O(n) — iterates and copies elements.
474
+
475
+ #### `Chunk.fromJavaIterator` — From Java Iterator
265
476
 
266
- Updates are immutable and use efficient buffering:
477
+ Consume a Java `Iterator` and collect its elements into a chunk:
478
+
479
+ ```scala
480
+ object Chunk {
481
+ def fromJavaIterator[A](iterator: java.util.Iterator[A]): Chunk[A]
482
+ }
483
+ ```
484
+
485
+ Building a chunk from a Java iterator:
267
486
 
268
487
  ```scala
269
488
  import zio.blocks.chunk.Chunk
489
+ import java.util.Arrays
490
+
491
+ val javaIter = Arrays.asList(1, 2, 3).iterator()
492
+ // javaIter: Iterator[Int] = java.util.Arrays$ArrayItr@29c50fed
493
+ val chunk = Chunk.fromJavaIterator(javaIter)
494
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
495
+ ```
496
+
497
+ **Performance:** O(n) — exhausts iterator and builds chunk.
498
+
499
+ ### Builder Methods
270
500
 
271
- val chunk = Chunk(1, 2, 3, 4, 5)
272
- val updated = chunk.updated(2, 100) // Chunk(1, 2, 100, 4, 5)
501
+ `ChunkBuilder[A]` is a mutable builder that accumulates elements incrementally and returns a `Chunk[A]` when complete. Use it when building chunks from elements that arrive one at a time or from streaming sources.
502
+
503
+ #### `Chunk.newBuilder` — Get a Builder
504
+
505
+ Obtain a fresh `ChunkBuilder` for incremental construction:
506
+
507
+ ```scala
508
+ object Chunk {
509
+ def newBuilder[A]: ChunkBuilder[A]
510
+ }
273
511
  ```
274
512
 
275
- ### Deduplication and Sorting
513
+ A builder integrates with Scala's collection factory pattern:
276
514
 
277
515
  ```scala
278
- import zio.blocks.chunk.Chunk
516
+ import zio.blocks.chunk.{Chunk, ChunkBuilder}
517
+
518
+ val builder = Chunk.newBuilder[Int]
519
+ // builder: ChunkBuilder[Int] = ChunkBuilder
520
+ builder.addOne(1)
521
+ // res21: ChunkBuilder[Int] = ChunkBuilder
522
+ builder.addOne(2)
523
+ // res22: ChunkBuilder[Int] = ChunkBuilder
524
+ val result = builder.result()
525
+ // result: Chunk[Int] = IndexedSeq(1, 2)
526
+ ```
527
+
528
+ ### Using ChunkBuilder for Incremental Construction
529
+
530
+ `ChunkBuilder[A]` is a mutable builder that accumulates elements and returns a `Chunk[A]`. Use it when building chunks from elements that arrive incrementally—over time, from streaming sources, or when the total size is unknown in advance.
279
531
 
280
- val withDupes = Chunk(1, 1, 2, 2, 2, 3, 3)
281
- val deduped = withDupes.dedupe // Chunk(1, 2, 3) - removes adjacent duplicates
532
+ When constructing a chunk from multiple sources, repeatedly calling `++` creates additional tree nodes and increases access depth (O(log n) per operation). Static construction methods like `Chunk.apply` or `Chunk.from` require all elements upfront. `ChunkBuilder` solves this by using an internal buffering strategy: elements accumulate in a small array, and only when full does the array append to the growing chunk tree. This yields O(1) amortized append cost—comparable to dynamically sized arrays, but without re-copying overhead. It is also the standard Scala mutable builder interface, so it integrates with `scala.collection` builders.
282
533
 
283
- val unsorted = Chunk(3, 1, 4, 1, 5)
284
- val sorted = unsorted.sorted // Chunk(1, 1, 3, 4, 5)
534
+ | Scenario | Right choice |
535
+ |--------------------------------------------------------------------------------------------|----------------------------------------------|
536
+ | Elements available all at once; constructing a static chunk | `Chunk.apply(...)` or `Chunk.from(iterable)` |
537
+ | Elements arrive incrementally; unknown size in advance; building from a stream or iterator | `ChunkBuilder` with `addOne` / `addAll` |
538
+
539
+ The signature of `ChunkBuilder` is:
540
+
541
+ ```scala
542
+ object ChunkBuilder {
543
+ def make[A](): ChunkBuilder[A]
544
+ def make[A](capacityHint: Int): ChunkBuilder[A]
545
+ }
285
546
  ```
286
547
 
287
- ### Splitting
548
+ Here's a realistic example: aggregating results from a paginated API that returns chunks of records until a sentinel response:
288
549
 
289
550
  ```scala
290
- import zio.blocks.chunk.Chunk
551
+ import zio.blocks.chunk.{Chunk, ChunkBuilder}
291
552
 
292
- val chunk = Chunk(1, 2, 3, 4, 5, 6)
553
+ case class ApiResponse(records: List[String], hasMore: Boolean)
554
+
555
+ def fetchAllRecords(): Chunk[String] = {
556
+ @annotation.tailrec
557
+ def loop(response: ApiResponse, builder: ChunkBuilder[String]): Chunk[String] = {
558
+ val builderWithRecords = builder.addAll(response.records.iterator)
559
+ if (response.hasMore) {
560
+ // Simulate fetching next page
561
+ loop(ApiResponse(List("record3", "record4"), false), builderWithRecords)
562
+ } else {
563
+ builderWithRecords.result()
564
+ }
565
+ }
566
+ loop(ApiResponse(List("record1", "record2"), true), ChunkBuilder.make[String](1000))
567
+ }
568
+ ```
569
+
570
+ In this scenario, the API may return 100 pages before completion. Using `Chunk.apply` would require buffering all responses in memory first. Using naive `++` concatenation repeatedly would create deep tree structures with O(log n) access overhead. `ChunkBuilder` handles pagination efficiently by maintaining a single O(1) amortized append mechanism throughout.
571
+
572
+ ## Core Operations
573
+
574
+ Chunk provides a rich set of operations for accessing, transforming, and combining elements. Operations are organized by category:
575
+
576
+ ### Element Access
577
+
578
+ Chunk provides several operations to inspect and retrieve individual elements efficiently. Whether you need random access by index, efficient access to boundaries, or to check the size, these methods offer fast, predictable performance:
579
+
580
+ #### `Chunk#apply` — Random Access
581
+
582
+ Access an element by index in O(log n) time (O(1) for array-backed chunks):
293
583
 
294
- val parts = chunk.split(3) // Chunk(Chunk(1,2), Chunk(3,4), Chunk(5,6))
295
- val (before, after) = chunk.splitWhere(_ > 3) // splits at first element > 3
584
+ ```scala
585
+ trait Chunk[+A] {
586
+ def apply(index: Int): A
587
+ }
296
588
  ```
297
589
 
298
- ### String Conversion
590
+ Access elements by index in a chunk:
299
591
 
300
592
  ```scala
301
593
  import zio.blocks.chunk.Chunk
302
- import java.nio.charset.StandardCharsets
303
594
 
304
- val bytes = Chunk[Byte](72, 101, 108, 108, 111)
305
- val str = bytes.asString // "Hello"
595
+ val chunk = Chunk(10, 20, 30, 40, 50)
596
+ ```
306
597
 
307
- val chars = Chunk('H', 'e', 'l', 'l', 'o')
308
- val str2 = chars.asString // "Hello"
598
+ Accessing by index returns individual elements:
309
599
 
310
- val withCharset = bytes.asString(StandardCharsets.UTF_8) // "Hello"
311
- val base64 = bytes.asBase64String // base64-encoded string
600
+ ```scala
601
+ chunk(0)
602
+ // res25: Int = 10
603
+ chunk(2)
604
+ // res26: Int = 30
605
+ chunk(4)
606
+ // res27: Int = 50
312
607
  ```
313
608
 
314
- ### Materialization
609
+ #### `Chunk#head` and `Chunk#last` — First and Last Elements
610
+
611
+ Access the first or last element:
612
+
613
+ ```scala
614
+ trait Chunk[+A] {
615
+ def head: A
616
+ def last: A
617
+ }
618
+ ```
315
619
 
316
- For complex operation chains, you can force materialization to an array-backed chunk:
620
+ Accessing the first or last element is efficient:
317
621
 
318
622
  ```scala
319
623
  import zio.blocks.chunk.Chunk
320
624
 
321
- val complex = Chunk(1, 2, 3) ++ Chunk(4, 5) ++ Chunk(6, 7)
322
- val materialized = complex.materialize // backed by a single array
625
+ val chunk = Chunk("a", "b", "c", "d")
323
626
  ```
324
627
 
325
- ## NonEmptyChunk
326
-
327
- `NonEmptyChunk[A]` is a chunk guaranteed to contain at least one element. This enables safe use of operations like `head` and `reduce`:
628
+ Getting the first and last elements is immediate:
328
629
 
329
630
  ```scala
330
- import zio.blocks.chunk.{Chunk, NonEmptyChunk}
631
+ chunk.head
632
+ // res29: String = "a"
633
+ chunk.last
634
+ // res30: String = "d"
635
+ ```
331
636
 
332
- val nec = NonEmptyChunk(1, 2, 3)
637
+ #### `Chunk#length` and `Chunk#size` — Chunk Size
333
638
 
334
- val first: Int = nec.head // always safe
335
- val sum: Int = nec.reduce(_ + _) // always safe
639
+ Get the number of elements (O(1) complexity):
336
640
 
337
- val mapped: NonEmptyChunk[Int] = nec.map(_ * 2)
338
- val flatMapped: NonEmptyChunk[Int] = nec.flatMap(n => NonEmptyChunk(n, n + 1))
641
+ ```scala
642
+ trait Chunk[+A] {
643
+ def length: Int
644
+ def size: Int
645
+ }
339
646
  ```
340
647
 
341
- ### Creating NonEmptyChunk
648
+ Getting the chunk size is an O(1) operation:
342
649
 
343
650
  ```scala
344
- import zio.blocks.chunk.{Chunk, NonEmptyChunk}
651
+ import zio.blocks.chunk.Chunk
345
652
 
346
- val fromValues = NonEmptyChunk(1, 2, 3)
347
- val single = NonEmptyChunk.single(42)
348
- val fromCons = NonEmptyChunk.fromCons(::(1, List(2, 3)))
349
- val fromIterable = NonEmptyChunk.fromIterable(1, List(2, 3))
653
+ val chunk = Chunk(1, 2, 3, 4, 5)
654
+ ```
350
655
 
351
- val maybeNec: Option[NonEmptyChunk[Int]] = NonEmptyChunk.fromChunk(Chunk(1, 2))
352
- val empty: Option[NonEmptyChunk[Int]] = NonEmptyChunk.fromChunk(Chunk.empty) // None
656
+ Both `length` and `size` return the element count:
657
+
658
+ ```scala
659
+ chunk.length
660
+ // res32: Int = 5
661
+ chunk.size
662
+ // res33: Int = 5
353
663
  ```
354
664
 
355
- ### Converting Between Chunk and NonEmptyChunk
665
+ #### `Chunk#headOption` and `Chunk#lastOption` — Safe First and Last
666
+
667
+ Like `head` and `last`, but return `Option` to safely handle empty chunks:
356
668
 
357
669
  ```scala
358
- import zio.blocks.chunk.{Chunk, NonEmptyChunk}
670
+ trait Chunk[+A] {
671
+ def headOption: Option[A]
672
+ def lastOption: Option[A]
673
+ }
674
+ ```
359
675
 
360
- val nec = NonEmptyChunk(1, 2, 3)
361
- val chunk: Chunk[Int] = nec.toChunk
676
+ Accessing the first or last element safely yields an `Option`:
362
677
 
363
- val chunk2 = Chunk(1, 2, 3)
364
- chunk2.nonEmptyOrElse(0)(_.reduce(_ + _)) // 6 if non-empty, 0 if empty
678
+ ```scala
679
+ import zio.blocks.chunk.Chunk
680
+
681
+ val chunk = Chunk("a", "b", "c")
682
+ val empty = Chunk.empty[Int]
365
683
  ```
366
684
 
367
- ### Operations That Preserve NonEmptiness
685
+ `headOption` and `lastOption` provide safe access:
686
+
687
+ ```scala
688
+ chunk.headOption
689
+ // res35: Option[String] = Some("a")
690
+ chunk.lastOption
691
+ // res36: Option[String] = Some("c")
692
+ empty.headOption
693
+ // res37: Option[Int] = None
694
+ empty.lastOption
695
+ // res38: Option[Int] = None
696
+ ```
368
697
 
369
- These operations return `NonEmptyChunk`:
370
- - `map`, `flatMap`, `flatten`
371
- - `append`, `prepend`, `++`
372
- - `zip`, `zipWith`, `zipWithIndex`
373
- - `sorted`, `sortBy`, `reverse`
374
- - `distinct`, `materialize`
698
+ #### `Chunk#indexWhere` — Find Index of Matching Element
375
699
 
376
- Operations that might produce empty results return `Chunk`:
377
- - `filter`, `filterNot`
378
- - `collect`
379
- - `tail`, `init`
700
+ Find the index of the first element that matches a predicate:
380
701
 
381
- ## ChunkBuilder
702
+ ```scala
703
+ trait Chunk[+A] {
704
+ def indexWhere(f: A => Boolean): Int
705
+ }
706
+ ```
382
707
 
383
- `ChunkBuilder` is a mutable builder for creating chunks efficiently. It's specialized for primitives to avoid boxing:
708
+ Searching for a matching element returns its index or -1 if not found:
384
709
 
385
710
  ```scala
386
- import zio.blocks.chunk.{Chunk, ChunkBuilder}
711
+ import zio.blocks.chunk.Chunk
387
712
 
388
- val builder = ChunkBuilder.make[Int]()
389
- builder.addOne(1)
390
- builder.addOne(2)
391
- builder.addAll(List(3, 4, 5))
392
- val result: Chunk[Int] = builder.result() // Chunk(1, 2, 3, 4, 5)
713
+ val numbers = Chunk(10, 20, 30, 40)
393
714
  ```
394
715
 
395
- ### Specialized Builders
396
-
397
- For primitives, use specialized builders for best performance:
716
+ Finding the index of the first even number:
398
717
 
399
718
  ```scala
400
- import zio.blocks.chunk.{Chunk, ChunkBuilder}
719
+ numbers.indexWhere(_ % 25 == 0)
720
+ // res40: Int = -1
721
+ numbers.indexWhere(_ % 7 == 0)
722
+ // res41: Int = -1
723
+ ```
724
+
725
+ #### `Chunk#tail` and `Chunk#init` — Rest and Initial Segments
401
726
 
402
- val intBuilder = new ChunkBuilder.Int
403
- intBuilder.addOne(1)
404
- intBuilder.addOne(2)
405
- val ints: Chunk[Int] = intBuilder.result()
727
+ `tail` returns all elements except the first, `init` returns all elements except the last:
406
728
 
407
- val byteBuilder = new ChunkBuilder.Byte
408
- val longBuilder = new ChunkBuilder.Long
409
- val doubleBuilder = new ChunkBuilder.Double
410
- val boolBuilder = new ChunkBuilder.Boolean
729
+ ```scala
730
+ trait Chunk[+A] {
731
+ def tail: Chunk[A]
732
+ def init: Chunk[A]
733
+ }
411
734
  ```
412
735
 
413
- Available specialized builders: `Boolean`, `Byte`, `Char`, `Short`, `Int`, `Long`, `Float`, `Double`
736
+ Taking the rest of the chunk after the first element, or all but the last:
414
737
 
415
- ## Bit Operations
738
+ ```scala
739
+ import zio.blocks.chunk.Chunk
416
740
 
417
- Chunk provides efficient bit-level operations for working with binary data:
741
+ val chunk = Chunk(1, 2, 3, 4)
742
+ ```
418
743
 
419
- ### Converting to Bits
744
+ `tail` drops the first element, `init` drops the last:
420
745
 
421
746
  ```scala
422
- import zio.blocks.chunk.Chunk
747
+ chunk.tail
748
+ // res43: Chunk[Int] = IndexedSeq(2, 3, 4)
749
+ chunk.init
750
+ // res44: Chunk[Int] = IndexedSeq(1, 2, 3)
751
+ ```
752
+
753
+ ### Transformations
423
754
 
424
- val bytes = Chunk[Byte](0x0F, 0xF0.toByte)
425
- val bits = bytes.asBitsByte // Chunk of 16 booleans
755
+ Chunk supports functional transformations that let you shape your data in powerful ways. Map applies a function to every element, flatMap chains operations and flattens results, filter keeps only elements that matter, and collect extracts values from nested structures:
426
756
 
427
- val ints = Chunk(0x12345678)
428
- val intBits = ints.asBitsInt(Chunk.BitChunk.Endianness.BigEndian)
757
+ #### `Chunk#map` — Transform Elements
429
758
 
430
- val longs = Chunk(0x123456789ABCDEF0L)
431
- val longBits = longs.asBitsLong(Chunk.BitChunk.Endianness.BigEndian)
759
+ Apply a function to each element:
760
+
761
+ ```scala
762
+ trait Chunk[+A] {
763
+ def map[B](f: A => B): Chunk[B]
764
+ }
432
765
  ```
433
766
 
434
- ### Bitwise Operations
767
+ Mapping a function across elements creates a new chunk:
435
768
 
436
769
  ```scala
437
770
  import zio.blocks.chunk.Chunk
438
771
 
439
- val a = Chunk(true, false, true, false)
440
- val b = Chunk(true, true, false, false)
772
+ val numbers = Chunk(1, 2, 3, 4)
773
+ // numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
774
+ val doubled = numbers.map(_ * 2)
775
+ // doubled: Chunk[Int] = IndexedSeq(2, 4, 6, 8)
776
+
777
+ val strings = Chunk("hello", "world")
778
+ // strings: Chunk[String] = IndexedSeq("hello", "world")
779
+ val lengths = strings.map(_.length)
780
+ // lengths: Chunk[Int] = IndexedSeq(5, 5)
781
+ ```
782
+
783
+ #### `Chunk#flatMap` — Flat Transformation
784
+
785
+ Map each element to an iterable collection and flatten the result:
441
786
 
442
- val andResult = a & b // Chunk(true, false, false, false)
443
- val orResult = a | b // Chunk(true, true, true, false)
444
- val xorResult = a ^ b // Chunk(false, true, true, false)
445
- val negated = a.negate // Chunk(false, true, false, true)
787
+ ```scala
788
+ trait Chunk[+A] {
789
+ def flatMap[B](f: A => IterableOnce[B]): Chunk[B]
790
+ }
446
791
  ```
447
792
 
448
- ### Packing Booleans
793
+ Flat-mapping chains transformations and flattens the result. The function can return any `IterableOnce` (such as `Chunk`, `List`, `Vector`, `Array`, etc.):
449
794
 
450
795
  ```scala
451
796
  import zio.blocks.chunk.Chunk
452
797
 
453
- val bits = Chunk(true, false, true, false, true, true, true, true)
454
- val packedBytes: Chunk[Byte] = bits.toPackedByte // Efficient byte representation
798
+ val numbers = Chunk(1, 2, 3)
799
+ // numbers: Chunk[Int] = IndexedSeq(1, 2, 3)
800
+ val expanded = numbers.flatMap(n => Chunk(n, n * 10))
801
+ // expanded: Chunk[Int] = IndexedSeq(1, 10, 2, 20, 3, 30)
802
+ ```
803
+
804
+ #### `Chunk#filter` — Keep Matching Elements
805
+
806
+ Keep only elements that match a predicate:
455
807
 
456
- val packedInts: Chunk[Int] = bits.toPackedInt(Chunk.BitChunk.Endianness.BigEndian)
457
- val packedLongs: Chunk[Long] = bits.toPackedLong(Chunk.BitChunk.Endianness.BigEndian)
808
+ ```scala
809
+ trait Chunk[+A] {
810
+ def filter(f: A => Boolean): Chunk[A]
811
+ }
458
812
  ```
459
813
 
460
- ### Binary String
814
+ Filtering by a predicate keeps only matching elements:
461
815
 
462
816
  ```scala
463
817
  import zio.blocks.chunk.Chunk
464
818
 
465
- val bits = Chunk(true, false, true, true)
466
- val binary: String = bits.toBinaryString // "1011"
819
+ val numbers = Chunk(1, 2, 3, 4, 5, 6)
820
+ // numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6)
821
+ val evens = numbers.filter(_ % 2 == 0)
822
+ // evens: Chunk[Int] = IndexedSeq(2, 4, 6)
823
+
824
+ val longWords = Chunk("a", "hello", "b", "world")
825
+ // longWords: Chunk[String] = IndexedSeq("a", "hello", "b", "world")
826
+ val filtered = longWords.filter(_.length > 1)
827
+ // filtered: Chunk[String] = IndexedSeq("hello", "world")
467
828
  ```
468
829
 
469
- ## ChunkMap
830
+ #### `Chunk#collect` — Filter-Map Combined
470
831
 
471
- `ChunkMap[K, V]` is an order-preserving immutable map backed by parallel chunks. It maintains insertion order during iteration:
832
+ Apply a partial function, keeping only successful matches:
472
833
 
473
834
  ```scala
474
- import zio.blocks.chunk.{Chunk, ChunkMap}
475
-
476
- val map = ChunkMap("a" -> 1, "b" -> 2, "c" -> 3)
477
-
478
- val value = map.get("b") // Some(2)
479
- val updated = map.updated("d", 4)
480
- val removed = map.removed("b")
835
+ trait Chunk[+A] {
836
+ def collect[B](pf: PartialFunction[A, B]): Chunk[B]
837
+ }
481
838
  ```
482
839
 
483
- ### Creating ChunkMap
840
+ Collecting combines filtering and mapping in one operation:
484
841
 
485
842
  ```scala
486
- import zio.blocks.chunk.{Chunk, ChunkMap}
843
+ import zio.blocks.chunk.Chunk
487
844
 
488
- val empty = ChunkMap.empty[String, Int]
489
- val fromPairs = ChunkMap("x" -> 1, "y" -> 2)
490
- val fromChunk = ChunkMap.fromChunk(Chunk(("a", 1), ("b", 2)))
491
- val fromChunks = ChunkMap.fromChunks(Chunk("a", "b"), Chunk(1, 2))
845
+ val values: Chunk[Any] = Chunk(1, "hello", 2, "world", 3)
846
+ // values: Chunk[Any] = IndexedSeq(1, "hello", 2, "world", 3)
847
+ val onlyInts = values.collect { case n: Int => n * 10 }
848
+ // onlyInts: Chunk[Int] = IndexedSeq(10, 20, 30)
492
849
  ```
493
850
 
494
- ### Indexed Access
851
+ #### `Chunk#sorted` — Sort Elements
495
852
 
496
- ChunkMap provides O(1) positional access:
853
+ Sort elements using an ordering:
497
854
 
498
855
  ```scala
499
- import zio.blocks.chunk.{Chunk, ChunkMap}
856
+ trait Chunk[+A] {
857
+ def sorted[A1 >: A](implicit ord: Ordering[A1]): Chunk[A]
858
+ }
859
+ ```
500
860
 
501
- val map = ChunkMap("z" -> 1, "a" -> 2, "m" -> 3)
861
+ Sorting arranges elements in order:
862
+
863
+ ```scala
864
+ import zio.blocks.chunk.Chunk
502
865
 
503
- val first = map.atIndex(0) // ("z", 1)
504
- val key = map.keyAtIndex(1) // "a"
505
- val value = map.valueAtIndex(2) // 3
866
+ val unsorted = Chunk(3, 1, 4, 1, 5, 9, 2, 6)
867
+ // unsorted: Chunk[Int] = IndexedSeq(3, 1, 4, 1, 5, 9, 2, 6)
868
+ val sorted = unsorted.sorted
869
+ // sorted: Chunk[Int] = IndexedSeq(1, 1, 2, 3, 4, 5, 6, 9)
506
870
 
507
- val keys: Chunk[String] = map.keysChunk
508
- val values: Chunk[Int] = map.valuesChunk
871
+ val strings = Chunk("zebra", "apple", "banana")
872
+ // strings: Chunk[String] = IndexedSeq("zebra", "apple", "banana")
873
+ val sortedStrings = strings.sorted
874
+ // sortedStrings: Chunk[String] = IndexedSeq("apple", "banana", "zebra")
509
875
  ```
510
876
 
511
- ### Optimized Lookup
877
+ #### `Chunk#sortBy` — Sort by Key
512
878
 
513
- For frequent lookups, create an indexed version with O(1) key access:
879
+ Sort elements according to a key function:
514
880
 
515
881
  ```scala
516
- import zio.blocks.chunk.ChunkMap
882
+ trait Chunk[+A] {
883
+ def sortBy[B](f: A => B)(implicit ord: Ordering[B]): Chunk[A]
884
+ }
885
+ ```
517
886
 
518
- val map = ChunkMap("a" -> 1, "b" -> 2, "c" -> 3)
519
- val indexed = map.indexed // O(1) lookups, extra memory for index
887
+ Sorting by a key produces a new ordered chunk:
520
888
 
521
- val value = indexed.get("b") // O(1) instead of O(n)
522
- ```
889
+ ```scala
890
+ import zio.blocks.chunk.Chunk
523
891
 
524
- ## Scala Collections Integration
892
+ case class Person(name: String, age: Int)
893
+ val people = Chunk(Person("Alice", 32), Person("Bob", 25), Person("Carol", 40))
894
+ // people: Chunk[Person] = IndexedSeq(
895
+ // Person(name = "Alice", age = 32),
896
+ // Person(name = "Bob", age = 25),
897
+ // Person(name = "Carol", age = 40)
898
+ // )
899
+ ```
525
900
 
526
- Chunk implements `IndexedSeq` and integrates seamlessly with Scala collections:
901
+ Sorting by age orders the people from youngest to oldest:
527
902
 
528
903
  ```scala
529
- import zio.blocks.chunk.Chunk
904
+ people.sortBy(_.age)
905
+ // res51: Chunk[Person] = IndexedSeq(
906
+ // Person(name = "Bob", age = 25),
907
+ // Person(name = "Alice", age = 32),
908
+ // Person(name = "Carol", age = 40)
909
+ // )
910
+ ```
530
911
 
531
- val chunk = Chunk(1, 2, 3, 4, 5)
912
+ #### `Chunk#collectFirst` — Collect First Matching Partial Function
532
913
 
533
- val list: List[Int] = chunk.toList
534
- val vector: Vector[Int] = chunk.toVector
535
- val array: Array[Int] = chunk.toArray
914
+ Apply a partial function to the first matching element and return the result as an option:
536
915
 
537
- val fromSeq: Chunk[Int] = Chunk.from(Vector(1, 2, 3))
916
+ ```scala
917
+ trait Chunk[+A] {
918
+ def collectFirst[B](pf: PartialFunction[A, B]): Option[B]
919
+ }
538
920
  ```
539
921
 
540
- Standard collection operations work as expected:
922
+ Collecting the first match returns an optional result:
541
923
 
542
924
  ```scala
543
925
  import zio.blocks.chunk.Chunk
544
926
 
545
- val chunk = Chunk(1, 2, 3)
546
- val result = chunk
547
- .filter(_ > 1)
548
- .map(_ * 2)
549
- .flatMap(n => Chunk(n, n + 1))
550
- ```
551
-
552
- ## Performance Characteristics
553
-
554
- | Operation | Time Complexity | Notes |
555
- |-----------|-----------------|-------|
556
- | `apply(i)` | O(1) | Direct array access for materialized chunks |
557
- | `length` | O(1) | Cached |
558
- | `head`, `last` | O(1) | |
559
- | `++` | O(log n) | Balanced tree concatenation |
560
- | `:+`, `+:` | O(1) amortized | Buffered appends |
561
- | `take`, `drop`, `slice` | O(1) | Creates view |
562
- | `map`, `filter`, `flatMap` | O(n) | |
563
- | `updated` | O(1) amortized | Buffered updates |
564
- | `materialize` | O(n) | Copies to array |
565
-
566
- ### When to Materialize
567
-
568
- Chunk automatically materializes when:
569
- - Tree depth exceeds internal thresholds
570
- - You call `materialize` explicitly
571
- - Converting to array with `toArray`
572
-
573
- Consider explicit materialization when:
574
- - Performing many random accesses on a deeply nested chunk
575
- - Passing data to APIs that need arrays
576
- - Optimizing a hot loop
927
+ val values = Chunk(1, "two", 3, "four")
928
+ ```
929
+
930
+ Extract the first string if present:
931
+
932
+ ```scala
933
+ values.collectFirst { case s: String => s }
934
+ // res53: Option[String] = Some("two")
935
+ ```
936
+
937
+ #### `Chunk#filterNot` — Keep Non-Matching Elements
938
+
939
+ Keep only elements that do NOT match a predicate:
940
+
941
+ ```scala
942
+ trait Chunk[+A] {
943
+ def filterNot(f: A => Boolean): Chunk[A]
944
+ }
945
+ ```
946
+
947
+ Filtering with a negated predicate removes elements that satisfy the condition:
948
+
949
+ ```scala
950
+ import zio.blocks.chunk.Chunk
951
+
952
+ val numbers = Chunk(1, 2, 3, 4, 5, 6)
953
+ // numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6)
954
+ ```
955
+
956
+ Keep only odd numbers:
957
+
958
+ ```scala
959
+ numbers.filterNot(_ % 2 == 0)
960
+ // res55: Chunk[Int] = IndexedSeq(1, 3, 5)
961
+ ```
962
+
963
+ #### `Chunk#distinct` — Remove All Duplicates
964
+
965
+ Remove duplicate elements, keeping only one occurrence of each unique value:
966
+
967
+ ```scala
968
+ trait Chunk[+A] {
969
+ def distinct: Chunk[A]
970
+ }
971
+ ```
972
+
973
+ Distinct eliminates duplicates based on equality:
974
+
975
+ ```scala
976
+ import zio.blocks.chunk.Chunk
977
+
978
+ val withDupes = Chunk(1, 2, 3, 2, 4, 1, 5)
979
+ ```
980
+
981
+ Keeping only unique values:
982
+
983
+ ```scala
984
+ withDupes.distinct
985
+ // res57: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
986
+ ```
987
+
988
+ #### `Chunk#dedupe` — Remove Consecutive Duplicates
989
+
990
+ Remove only consecutive duplicate elements, preserving the first occurrence in each run:
991
+
992
+ ```scala
993
+ trait Chunk[+A] {
994
+ def dedupe: Chunk[A]
995
+ }
996
+ ```
997
+
998
+ Deduplication differs from `distinct` by only collapsing adjacent duplicates:
999
+
1000
+ ```scala
1001
+ import zio.blocks.chunk.Chunk
1002
+
1003
+ val runs = Chunk(1, 1, 2, 2, 2, 3, 1, 1)
1004
+ ```
1005
+
1006
+ Removing consecutive repeats yields:
1007
+
1008
+ ```scala
1009
+ runs.dedupe
1010
+ // res59: Chunk[Int] = IndexedSeq(1, 2, 3, 1)
1011
+ ```
1012
+
1013
+ #### `Chunk#flatten` — Flatten Nested Chunks
1014
+
1015
+ Flatten a chunk of chunks into a single chunk:
1016
+
1017
+ ```scala
1018
+ trait Chunk[+A] {
1019
+ def flatten[B](implicit ev: A <:< Chunk[B]): Chunk[B]
1020
+ }
1021
+ ```
1022
+
1023
+ Flattening concatenates nested chunks:
1024
+
1025
+ ```scala
1026
+ import zio.blocks.chunk.Chunk
1027
+
1028
+ val nested = Chunk(Chunk(1, 2), Chunk(3), Chunk(4, 5))
1029
+ // nested: Chunk[Chunk[Int]] = IndexedSeq(
1030
+ // IndexedSeq(1, 2),
1031
+ // IndexedSeq(3),
1032
+ // IndexedSeq(4, 5)
1033
+ // )
1034
+ ```
1035
+
1036
+ A single flattened chunk results:
1037
+
1038
+ ```scala
1039
+ nested.flatten
1040
+ // res61: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
1041
+ ```
1042
+
1043
+ #### `Chunk#mapAccum` — Stateful Map with Accumulator
1044
+
1045
+ Map over elements while accumulating state, returning the final state and the transformed chunk:
1046
+
1047
+ ```scala
1048
+ trait Chunk[+A] {
1049
+ def mapAccum[S, B](s0: S)(f: (S, A) => (S, B)): (S, Chunk[B])
1050
+ }
1051
+ ```
1052
+
1053
+ Accumulating state during mapping threads an accumulator through:
1054
+
1055
+ ```scala
1056
+ import zio.blocks.chunk.Chunk
1057
+
1058
+ val nums = Chunk(10, 20, 30)
1059
+ ```
1060
+
1061
+ Starting with sum zero, accumulate running totals:
1062
+
1063
+ ```scala
1064
+ val (finalSum, result) = nums.mapAccum(0) { (sum, n) => (sum + n, sum + n) }
1065
+ // finalSum: Int = 60
1066
+ // result: Chunk[Int] = IndexedSeq(10, 30, 60)
1067
+ finalSum
1068
+ // res63: Int = 60
1069
+ result
1070
+ // res64: Chunk[Int] = IndexedSeq(10, 30, 60)
1071
+ ```
1072
+
1073
+ #### `Chunk#reverse` — Reverse Order
1074
+
1075
+ Reverse the order of elements in the chunk:
1076
+
1077
+ ```scala
1078
+ trait Chunk[+A] {
1079
+ def reverse: Chunk[A]
1080
+ }
1081
+ ```
1082
+
1083
+ Reversing creates a new chunk with elements in opposite order:
1084
+
1085
+ ```scala
1086
+ import zio.blocks.chunk.Chunk
1087
+
1088
+ val original = Chunk(1, 2, 3, 4)
1089
+ // original: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
1090
+ ```
1091
+
1092
+ Reverse produces:
1093
+
1094
+ ```scala
1095
+ original.reverse
1096
+ // res66: Chunk[Int] = IndexedSeq(4, 3, 2, 1)
1097
+ ```
1098
+
1099
+ ### Combining Chunks
1100
+
1101
+ When you need to bring multiple chunks together, Chunk provides efficient operations to merge them. Concatenation uses a balanced tree structure for fast repeated joins, you can append or prepend single elements, and zip operations pair elements from parallel sequences:
1102
+
1103
+ #### `Chunk#++(that)` — Concatenation
1104
+
1105
+ Combine two chunks. Uses balanced tree structure for efficiency:
1106
+
1107
+ ```scala
1108
+ trait Chunk[+A] {
1109
+ def ++[A1 >: A](that: Chunk[A1]): Chunk[A1]
1110
+ }
1111
+ ```
1112
+
1113
+ Concatenating two chunks combines them efficiently:
1114
+
1115
+ ```scala
1116
+ import zio.blocks.chunk.Chunk
1117
+
1118
+ val chunk1 = Chunk(1, 2, 3)
1119
+ // chunk1: Chunk[Int] = IndexedSeq(1, 2, 3)
1120
+ val chunk2 = Chunk(4, 5, 6)
1121
+ // chunk2: Chunk[Int] = IndexedSeq(4, 5, 6)
1122
+ val combined = chunk1 ++ chunk2
1123
+ // combined: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6)
1124
+
1125
+ // Efficient even with many concatenations
1126
+ val many = (1 to 100).foldLeft(Chunk.empty[Int]) { (acc, i) =>
1127
+ acc ++ Chunk(i)
1128
+ }
1129
+ // many: Chunk[Int] = IndexedSeq(
1130
+ // 1,
1131
+ // 2,
1132
+ // 3,
1133
+ // 4,
1134
+ // 5,
1135
+ // 6,
1136
+ // 7,
1137
+ // 8,
1138
+ // 9,
1139
+ // 10,
1140
+ // 11,
1141
+ // 12,
1142
+ // 13,
1143
+ // 14,
1144
+ // 15,
1145
+ // 16,
1146
+ // 17,
1147
+ // 18,
1148
+ // 19,
1149
+ // 20,
1150
+ // 21,
1151
+ // 22,
1152
+ // 23,
1153
+ // 24,
1154
+ // 25,
1155
+ // 26,
1156
+ // 27,
1157
+ // 28,
1158
+ // 29,
1159
+ // 30,
1160
+ // 31,
1161
+ // 32,
1162
+ // 33,
1163
+ // 34,
1164
+ // 35,
1165
+ // 36,
1166
+ // 37,
1167
+ // 38,
1168
+ // 39,
1169
+ // 40,
1170
+ // 41,
1171
+ // 42,
1172
+ // 43,
1173
+ // 44,
1174
+ // 45,
1175
+ // 46,
1176
+ // 47,
1177
+ // 48,
1178
+ // ...
1179
+ ```
1180
+
1181
+ #### `Chunk#:+(a)` — Append Element
1182
+
1183
+ Append a single element to the end:
1184
+
1185
+ ```scala
1186
+ trait Chunk[+A] {
1187
+ def :+[A1 >: A](a: A1): Chunk[A1]
1188
+ }
1189
+ ```
1190
+
1191
+ Appending a single element creates a new chunk:
1192
+
1193
+ ```scala
1194
+ import zio.blocks.chunk.Chunk
1195
+
1196
+ val chunk = Chunk(1, 2, 3)
1197
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
1198
+ val appended = chunk :+ 4
1199
+ // appended: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
1200
+ ```
1201
+
1202
+ #### `Chunk#+:(a)` — Prepend Element
1203
+
1204
+ Prepend a single element to the beginning:
1205
+
1206
+ ```scala
1207
+ trait Chunk[+A] {
1208
+ def +:[A1 >: A](a: A1): Chunk[A1]
1209
+ }
1210
+ ```
1211
+
1212
+ Prepending a single element adds it to the front:
1213
+
1214
+ ```scala
1215
+ import zio.blocks.chunk.Chunk
1216
+
1217
+ val chunk = Chunk(2, 3, 4)
1218
+ // chunk: Chunk[Int] = IndexedSeq(2, 3, 4)
1219
+ val prepended = 1 +: chunk
1220
+ // prepended: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
1221
+ ```
1222
+
1223
+ #### `Chunk#zip` and `Chunk#zipWith` — Combine Parallel Chunks
1224
+
1225
+ Combine two chunks element-wise:
1226
+
1227
+ ```scala
1228
+ trait Chunk[+A] {
1229
+ def zip[B](that: Chunk[B]): Chunk[(A, B)]
1230
+ def zipWith[B, C](that: Chunk[B])(f: (A, B) => C): Chunk[C]
1231
+ }
1232
+ ```
1233
+
1234
+ Zipping combines elements from two chunks:
1235
+
1236
+ ```scala
1237
+ import zio.blocks.chunk.Chunk
1238
+
1239
+ val numbers = Chunk(1, 2, 3)
1240
+ // numbers: Chunk[Int] = IndexedSeq(1, 2, 3)
1241
+ val letters = Chunk("a", "b", "c")
1242
+ // letters: Chunk[String] = IndexedSeq("a", "b", "c")
1243
+
1244
+ val zipped = numbers.zip(letters)
1245
+ // zipped: Chunk[Tuple2[Int, String]] = IndexedSeq(
1246
+ // (1, "a"),
1247
+ // (2, "b"),
1248
+ // (3, "c")
1249
+ // )
1250
+
1251
+ val combined = numbers.zipWith(letters)((n, l) => s"$l$n")
1252
+ // combined: Chunk[String] = IndexedSeq("a1", "b2", "c3")
1253
+ ```
1254
+
1255
+ #### `Chunk#appended` — Append Element (Method Form)
1256
+
1257
+ Append a single element to the end (named method equivalent of `:+`):
1258
+
1259
+ ```scala
1260
+ trait Chunk[+A] {
1261
+ def appended[A1 >: A](a: A1): Chunk[A1]
1262
+ }
1263
+ ```
1264
+
1265
+ Appending creates a new chunk:
1266
+
1267
+ ```scala
1268
+ import zio.blocks.chunk.Chunk
1269
+
1270
+ val chunk = Chunk(1, 2, 3)
1271
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
1272
+ val appended = chunk.appended(4)
1273
+ // appended: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
1274
+ ```
1275
+
1276
+ #### `Chunk#prepended` — Prepend Element (Method Form)
1277
+
1278
+ Prepend a single element to the beginning (named method equivalent of `+:`):
1279
+
1280
+ ```scala
1281
+ trait Chunk[+A] {
1282
+ def prepended[A1 >: A](a: A1): Chunk[A1]
1283
+ }
1284
+ ```
1285
+
1286
+ Prepending adds an element to the front:
1287
+
1288
+ ```scala
1289
+ import zio.blocks.chunk.Chunk
1290
+
1291
+ val chunk = Chunk(2, 3, 4)
1292
+ // chunk: Chunk[Int] = IndexedSeq(2, 3, 4)
1293
+ val prepended = chunk.prepended(1)
1294
+ // prepended: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
1295
+ ```
1296
+
1297
+ #### `Chunk#zipAll` — Zip with Defaults for Unequal Lengths
1298
+
1299
+ Combine two chunks element-wise, using default values when one chunk is shorter:
1300
+
1301
+ ```scala
1302
+ trait Chunk[+A] {
1303
+ def zipAll[B](that: Chunk[B]): Chunk[(Option[A], Option[B])]
1304
+ }
1305
+ ```
1306
+
1307
+ Zipping with defaults ensures both sides always have a value:
1308
+
1309
+ ```scala
1310
+ import zio.blocks.chunk.Chunk
1311
+
1312
+ val left = Chunk(1, 2, 3)
1313
+ val right = Chunk("a", "b")
1314
+ ```
1315
+
1316
+ The shorter chunk is padded with `None` values:
1317
+
1318
+ ```scala
1319
+ left.zipAll(right)
1320
+ // res74: Chunk[Tuple2[Option[Int], Option[String]]] = IndexedSeq(
1321
+ // (Some(1), Some("a")),
1322
+ // (Some(2), Some("b")),
1323
+ // (Some(3), None)
1324
+ // )
1325
+ ```
1326
+
1327
+ #### `Chunk#zipAllWith` — Zip with Custom Combiner
1328
+
1329
+ Zip two chunks element-wise with a custom combiner function that handles missing elements:
1330
+
1331
+ ```scala
1332
+ trait Chunk[+A] {
1333
+ def zipAllWith[B, C](that: Chunk[B])(left: A => C, right: B => C)(both: (A, B) => C): Chunk[C]
1334
+ }
1335
+ ```
1336
+
1337
+ Providing separate handlers for left-only, right-only, and both:
1338
+
1339
+ ```scala
1340
+ import zio.blocks.chunk.Chunk
1341
+
1342
+ val left = Chunk(1, 2, 3)
1343
+ // left: Chunk[Int] = IndexedSeq(1, 2, 3)
1344
+ val right = Chunk("a", "b")
1345
+ // right: Chunk[String] = IndexedSeq("a", "b")
1346
+ ```
1347
+
1348
+ Combine with default for missing elements:
1349
+
1350
+ ```scala
1351
+ left.zipAllWith(right)(
1352
+ a => s"missing:${a}", // left only
1353
+ b => s"only:${b}" // right only
1354
+ )(
1355
+ (a, b) => s"$a:$b" // both present
1356
+ )
1357
+ // res76: Chunk[String] = IndexedSeq("1:a", "2:b", "missing:3")
1358
+ ```
1359
+
1360
+ #### `Chunk#zipWithIndex` — Zip with Index Starting at Zero
1361
+
1362
+ Zip each element with its zero-based index:
1363
+
1364
+ ```scala
1365
+ trait Chunk[+A] {
1366
+ def zipWithIndex: Chunk[(A, Int)]
1367
+ }
1368
+ ```
1369
+
1370
+ Attaching indices is useful for positional awareness:
1371
+
1372
+ ```scala
1373
+ import zio.blocks.chunk.Chunk
1374
+
1375
+ val words = Chunk("alpha", "beta", "gamma")
1376
+ ```
1377
+
1378
+ Zipping with index:
1379
+
1380
+ ```scala
1381
+ words.zipWithIndex
1382
+ // res78: Chunk[Tuple2[String, Int]] = IndexedSeq(
1383
+ // ("alpha", 0),
1384
+ // ("beta", 1),
1385
+ // ("gamma", 2)
1386
+ // )
1387
+ ```
1388
+
1389
+ #### `Chunk#updated` — Update Element at Index
1390
+
1391
+ Update the element at a given index, returning a new chunk:
1392
+
1393
+ ```scala
1394
+ trait Chunk[+A] {
1395
+ override def updated[A1 >: A](index: Int, elem: A1): Chunk[A1]
1396
+ }
1397
+ ```
1398
+
1399
+ The `updated` method creates a new chunk with the element at the specified index replaced. The original chunk remains unchanged:
1400
+
1401
+ ```scala
1402
+ import zio.blocks.chunk.Chunk
1403
+
1404
+ val chunk = Chunk(1, 2, 3, 4, 5)
1405
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
1406
+
1407
+ val updated = chunk.updated(2, 99)
1408
+ // updated: Chunk[Int] = IndexedSeq(1, 2, 99, 4, 5)
1409
+ ```
1410
+
1411
+ ### Slicing and Partitioning
1412
+
1413
+ Often you need to work with portions of a chunk rather than the whole. These operations let you keep elements from the ends, skip unwanted portions, extract contiguous ranges by position, and intelligently partition chunks based on predicates or conditions:
1414
+
1415
+ #### `Chunk#take` and `Chunk#takeRight` — Take from Ends
1416
+
1417
+ Take the first n elements or last n elements:
1418
+
1419
+ ```scala
1420
+ trait Chunk[+A] {
1421
+ def take(n: Int): Chunk[A]
1422
+ def takeRight(n: Int): Chunk[A]
1423
+ }
1424
+ ```
1425
+
1426
+ Taking elements from the beginning or end creates a new chunk:
1427
+
1428
+ ```scala
1429
+ import zio.blocks.chunk.Chunk
1430
+
1431
+ val chunk = Chunk(1, 2, 3, 4, 5)
1432
+ ```
1433
+
1434
+ Taking from the beginning or end produces new chunks:
1435
+
1436
+ ```scala
1437
+ chunk.take(3)
1438
+ // res81: Chunk[Int] = IndexedSeq(1, 2, 3)
1439
+ chunk.takeRight(2)
1440
+ // res82: Chunk[Int] = IndexedSeq(4, 5)
1441
+ ```
1442
+
1443
+ #### `Chunk#drop` and `Chunk#dropRight` — Remove from Ends
1444
+
1445
+ Remove the first n elements or last n elements:
1446
+
1447
+ ```scala
1448
+ trait Chunk[+A] {
1449
+ def drop(n: Int): Chunk[A]
1450
+ def dropRight(n: Int): Chunk[A]
1451
+ }
1452
+ ```
1453
+
1454
+ Dropping elements removes them from the beginning or end:
1455
+
1456
+ ```scala
1457
+ import zio.blocks.chunk.Chunk
1458
+
1459
+ val chunk = Chunk(1, 2, 3, 4, 5)
1460
+ ```
1461
+
1462
+ Dropping from beginning or end removes those elements:
1463
+
1464
+ ```scala
1465
+ chunk.drop(2)
1466
+ // res84: Chunk[Int] = IndexedSeq(3, 4, 5)
1467
+ chunk.dropRight(2)
1468
+ // res85: Chunk[Int] = IndexedSeq(1, 2, 3)
1469
+ ```
1470
+
1471
+ #### `Chunk#slice` — Extract a Range
1472
+
1473
+ Extract elements from a start index to an end index:
1474
+
1475
+ ```scala
1476
+ trait Chunk[+A] {
1477
+ def slice(from: Int, until: Int): Chunk[A]
1478
+ }
1479
+ ```
1480
+
1481
+ Slicing extracts a contiguous range of elements:
1482
+
1483
+ ```scala
1484
+ import zio.blocks.chunk.Chunk
1485
+
1486
+ val chunk = Chunk(10, 20, 30, 40, 50, 60)
1487
+ ```
1488
+
1489
+ Slicing from a start index to end index extracts the range:
1490
+
1491
+ ```scala
1492
+ chunk.slice(1, 4)
1493
+ // res87: Chunk[Int] = IndexedSeq(20, 30, 40)
1494
+ chunk.slice(2, 5)
1495
+ // res88: Chunk[Int] = IndexedSeq(30, 40, 50)
1496
+ ```
1497
+
1498
+ #### `Chunk#split` — Split into Equally-Sized Chunks
1499
+
1500
+ Split the chunk into N equally-sized chunks. When the chunk size is not evenly divisible, remainder elements are distributed into earlier chunks. If n exceeds the chunk's length, the result contains at most chunk.length single-element chunks.
1501
+
1502
+ Note: Passing `0` throws `ArithmeticException` due to division by zero. Negative values are not rejected by the current implementation and may produce unexpected results, so `n` should always be positive in normal use.
1503
+
1504
+ The method signature is:
1505
+
1506
+ ```scala
1507
+ trait Chunk[+A] {
1508
+ def split(n: Int): Chunk[Chunk[A]]
1509
+ }
1510
+ ```
1511
+
1512
+ Splitting divides a chunk into N equal parts:
1513
+
1514
+ ```scala
1515
+ import zio.blocks.chunk.Chunk
1516
+
1517
+ val chunk = Chunk(1, 2, 3, 4, 5, 6)
1518
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6)
1519
+ val splitInto2 = chunk.split(2)
1520
+ // splitInto2: Chunk[Chunk[Int]] = IndexedSeq(
1521
+ // IndexedSeq(1, 2, 3),
1522
+ // IndexedSeq(4, 5, 6)
1523
+ // )
1524
+
1525
+ val uneven = Chunk(1, 2, 3, 4, 5, 6, 7)
1526
+ // uneven: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6, 7)
1527
+ val splitInto3 = uneven.split(3)
1528
+ // splitInto3: Chunk[Chunk[Int]] = IndexedSeq(
1529
+ // IndexedSeq(1, 2, 3),
1530
+ // IndexedSeq(4, 5),
1531
+ // IndexedSeq(6, 7)
1532
+ // )
1533
+ ```
1534
+
1535
+ #### `Chunk#span` and `Chunk#splitWhere` — Partition by Predicate
1536
+
1537
+ `span(f)` splits the chunk into a prefix where the predicate holds, and the remainder. It stops at the first element where the predicate becomes false.
1538
+
1539
+ `splitWhere(f)` is similar but has inverted logic: it splits at the first element where the predicate becomes true.
1540
+
1541
+ The method signatures are:
1542
+
1543
+ ```scala
1544
+ trait Chunk[+A] {
1545
+ def span(f: A => Boolean): (Chunk[A], Chunk[A])
1546
+ def splitWhere(f: A => Boolean): (Chunk[A], Chunk[A])
1547
+ }
1548
+ ```
1549
+
1550
+ Partitioning by predicate creates two chunks:
1551
+
1552
+ ```scala
1553
+ import zio.blocks.chunk.Chunk
1554
+
1555
+ val numbers = Chunk(1, 2, 3, 4, 5, 6)
1556
+ // numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6)
1557
+ val (prefix, rest) = numbers.span(_ < 4)
1558
+ // prefix: Chunk[Int] = IndexedSeq(1, 2, 3)
1559
+ // rest: Chunk[Int] = IndexedSeq(4, 5, 6)
1560
+ val (upTo, remaining) = Chunk(1, 2, 5, 3, 4).splitWhere(_ >= 5)
1561
+ // upTo: Chunk[Int] = IndexedSeq(1, 2)
1562
+ // remaining: Chunk[Int] = IndexedSeq(5, 3, 4)
1563
+ ```
1564
+
1565
+ #### `Chunk#dropWhile` — Drop While Predicate Holds
1566
+
1567
+ Drop elements from the beginning while a predicate is true:
1568
+
1569
+ ```scala
1570
+ trait Chunk[+A] {
1571
+ def dropWhile(f: A => Boolean): Chunk[A]
1572
+ }
1573
+ ```
1574
+
1575
+ Dropping while the predicate holds removes leading elements:
1576
+
1577
+ ```scala
1578
+ import zio.blocks.chunk.Chunk
1579
+
1580
+ val numbers = Chunk(1, 2, 3, 4, 5, 6)
1581
+ ```
1582
+
1583
+ Drop initial even numbers:
1584
+
1585
+ ```scala
1586
+ numbers.dropWhile(_ % 2 == 0)
1587
+ // res92: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6)
1588
+ ```
1589
+
1590
+ #### `Chunk#dropUntil` — Drop Until Predicate Fails
1591
+
1592
+ Drop elements until the predicate becomes false (i.e., drop while predicate holds, but stopped when condition fails):
1593
+
1594
+ ```scala
1595
+ trait Chunk[+A] {
1596
+ def dropUntil(f: A => Boolean): Chunk[A]
1597
+ }
1598
+ ```
1599
+
1600
+ Dropping until the predicate fails keeps the first failing element and the rest:
1601
+
1602
+ ```scala
1603
+ import zio.blocks.chunk.Chunk
1604
+
1605
+ val numbers = Chunk(1, 2, 3, 4, 5)
1606
+ // numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
1607
+ ```
1608
+
1609
+ Drop numbers less than 3:
1610
+
1611
+ ```scala
1612
+ numbers.dropUntil(_ >= 3)
1613
+ // res94: Chunk[Int] = IndexedSeq(4, 5)
1614
+ ```
1615
+
1616
+ #### `Chunk#takeWhile` — Take While Predicate Holds
1617
+
1618
+ Take elements from the beginning while a predicate is true, stopping at the first failure:
1619
+
1620
+ ```scala
1621
+ trait Chunk[+A] {
1622
+ def takeWhile(f: A => Boolean): Chunk[A]
1623
+ }
1624
+ ```
1625
+
1626
+ Taking while the predicate holds collects a prefix:
1627
+
1628
+ ```scala
1629
+ import zio.blocks.chunk.Chunk
1630
+
1631
+ val numbers = Chunk(1, 2, 3, 4, 5, 6)
1632
+ ```
1633
+
1634
+ Take the initial odd numbers:
1635
+
1636
+ ```scala
1637
+ numbers.takeWhile(_ % 2 != 0)
1638
+ // res96: Chunk[Int] = IndexedSeq(1)
1639
+ ```
1640
+
1641
+ #### `Chunk#splitAt` — Split at Index
1642
+
1643
+ Split the chunk into two chunks at a given index: the first contains elements `[0, until)`, the second contains the remainder.
1644
+
1645
+ The method signature is:
1646
+
1647
+ ```scala
1648
+ trait Chunk[+A] {
1649
+ def splitAt(n: Int): (Chunk[A], Chunk[A])
1650
+ }
1651
+ ```
1652
+
1653
+ Splitting creates two chunks from a single index:
1654
+
1655
+ ```scala
1656
+ import zio.blocks.chunk.Chunk
1657
+
1658
+ val chunk = Chunk(10, 20, 30, 40, 50)
1659
+ ```
1660
+
1661
+ Splitting at index 3:
1662
+
1663
+ ```scala
1664
+ chunk.splitAt(3)
1665
+ // res98: Tuple2[Chunk[Int], Chunk[Int]] = (
1666
+ // IndexedSeq(10, 20, 30),
1667
+ // IndexedSeq(40, 50)
1668
+ // )
1669
+ ```
1670
+
1671
+ ### Querying and Folding
1672
+
1673
+ Chunk enables you to ask questions about your data and reduce it to meaningful results. Use fold operations to accumulate values, test predicates to verify conditions hold across elements, and search for specific values that match your criteria:
1674
+
1675
+ #### `Chunk#foldLeft` — Left Fold
1676
+
1677
+ Process elements left-to-right with an accumulator:
1678
+
1679
+ ```scala
1680
+ trait Chunk[+A] {
1681
+ def foldLeft[S](s0: S)(f: (S, A) => S): S
1682
+ }
1683
+ ```
1684
+
1685
+ Left-folding accumulates values from left to right:
1686
+
1687
+ ```scala
1688
+ import zio.blocks.chunk.Chunk
1689
+
1690
+ val numbers = Chunk(1, 2, 3, 4, 5)
1691
+ // numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
1692
+
1693
+ val sum = numbers.foldLeft(0)(_ + _)
1694
+ // sum: Int = 15
1695
+
1696
+ val product = numbers.foldLeft(1)(_ * _)
1697
+ // product: Int = 120
1698
+
1699
+ val concat = Chunk("a", "b", "c").foldLeft("")(_ + _)
1700
+ // concat: String = "abc"
1701
+ ```
1702
+
1703
+ #### `Chunk#foldRight` — Right Fold
1704
+
1705
+ Process elements right-to-left with an accumulator:
1706
+
1707
+ ```scala
1708
+ trait Chunk[+A] {
1709
+ def foldRight[S](s0: S)(f: (A, S) => S): S
1710
+ }
1711
+ ```
1712
+
1713
+ Right-folding accumulates values from right to left:
1714
+
1715
+ ```scala
1716
+ import zio.blocks.chunk.Chunk
1717
+
1718
+ val numbers = Chunk(1, 2, 3, 4)
1719
+ // numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
1720
+ val result = numbers.foldRight(List[Int]())(_ :: _)
1721
+ // result: List[Int] = List(1, 2, 3, 4)
1722
+ ```
1723
+
1724
+ #### `Chunk#foldWhile` — Fold with Early Exit
1725
+
1726
+ Fold left with early termination based on a predicate on the accumulator:
1727
+
1728
+ ```scala
1729
+ trait Chunk[+A] {
1730
+ def foldWhile[S](s0: S)(pred: S => Boolean)(f: (S, A) => S): S
1731
+ }
1732
+ ```
1733
+
1734
+ `foldWhile` allows you to stop folding when a condition on the accumulator becomes false, avoiding unnecessary processing of remaining elements:
1735
+
1736
+ ```scala
1737
+ import zio.blocks.chunk.Chunk
1738
+
1739
+ val numbers = Chunk(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
1740
+ // numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
1741
+
1742
+ // Fold while accumulator is less than 20, adding elements
1743
+ val result = numbers.foldWhile(0)(acc => acc < 20)((acc, n) => acc + n)
1744
+ // result: Int = 21
1745
+ ```
1746
+
1747
+ #### `Chunk#exists` and `Chunk#forall` — Predicates
1748
+
1749
+ Check if any or all elements match a predicate:
1750
+
1751
+ ```scala
1752
+ trait Chunk[+A] {
1753
+ def exists(f: A => Boolean): Boolean
1754
+ def forall(f: A => Boolean): Boolean
1755
+ }
1756
+ ```
1757
+
1758
+ Testing predicates answers questions about elements:
1759
+
1760
+ ```scala
1761
+ import zio.blocks.chunk.Chunk
1762
+
1763
+ val numbers = Chunk(2, 4, 6, 8)
1764
+ val mixed = Chunk(1, 2, 3)
1765
+ ```
1766
+
1767
+ Testing existence and universal predicates returns Boolean:
1768
+
1769
+ ```scala
1770
+ numbers.exists(_ > 5)
1771
+ // res103: Boolean = true
1772
+ numbers.forall(_ % 2 == 0)
1773
+ // res104: Boolean = true
1774
+ mixed.forall(_ > 0)
1775
+ // res105: Boolean = true
1776
+ mixed.forall(_ % 2 == 0)
1777
+ // res106: Boolean = false
1778
+ ```
1779
+
1780
+ #### `Chunk#find` — First Matching Element
1781
+
1782
+ Find the first element matching a predicate:
1783
+
1784
+ ```scala
1785
+ trait Chunk[+A] {
1786
+ def find(f: A => Boolean): Option[A]
1787
+ }
1788
+ ```
1789
+
1790
+ Finding the first matching element returns an Option:
1791
+
1792
+ ```scala
1793
+ import zio.blocks.chunk.Chunk
1794
+
1795
+ val numbers = Chunk(1, 2, 3, 4, 5)
1796
+ val words = Chunk("apple", "banana", "cherry")
1797
+ ```
1798
+
1799
+ Finding returns Some for matches and None for no match:
1800
+
1801
+ ```scala
1802
+ numbers.find(_ > 3)
1803
+ // res108: Option[Int] = Some(4)
1804
+ numbers.find(_ > 10)
1805
+ // res109: Option[Int] = None
1806
+ words.find(_.startsWith("b"))
1807
+ // res110: Option[String] = Some("banana")
1808
+ ```
1809
+
1810
+ #### `Chunk#corresponds` — Check Parallel Correspondence
1811
+
1812
+ Check whether corresponding elements of two chunks satisfy a predicate in lockstep:
1813
+
1814
+ ```scala
1815
+ trait Chunk[+A] {
1816
+ def corresponds[B](that: Chunk[B])(f: (A, B) => Boolean): Boolean
1817
+ }
1818
+ ```
1819
+
1820
+ `corresponds` tests pairwise element compatibility:
1821
+
1822
+ ```scala
1823
+ import zio.blocks.chunk.Chunk
1824
+
1825
+ val a = Chunk(1, 2, 3)
1826
+ val b = Chunk(1, 2, 3)
1827
+ ```
1828
+
1829
+ Correspondence holds when all pairs match:
1830
+
1831
+ ```scala
1832
+ a.corresponds(b)(_ == _)
1833
+ // res112: Boolean = true
1834
+ ```
1835
+
1836
+ ### Grouping
1837
+
1838
+ Chunk provides powerful grouping operations to organize elements by keys or into fixed-size partitions. Use `groupBy` to categorize elements into maps of chunks, `groupMap` to combine grouping and mapping in a single pass, and `grouped` to split the chunk into chunks of a specified size.
1839
+
1840
+ #### `Chunk#groupBy` — Group Elements by Key
1841
+
1842
+ Group elements by a key function, producing a map from keys to chunks:
1843
+
1844
+ ```scala
1845
+ trait Chunk[+A] {
1846
+ def groupBy[K](f: A => K): Map[K, Chunk[A]]
1847
+ }
1848
+ ```
1849
+
1850
+ Grouping by a key aggregates related elements:
1851
+
1852
+ ```scala
1853
+ import zio.blocks.chunk.Chunk
1854
+
1855
+ case class Person(name: String, age: Int)
1856
+ val people = Chunk(Person("Alice", 32), Person("Bob", 25), Person("Carol", 32))
1857
+ ```
1858
+
1859
+ Group people by age:
1860
+
1861
+ ```scala
1862
+ people.groupBy(_.age)
1863
+ // res114: Map[Int, Chunk[Person]] = Map(
1864
+ // 25 -> IndexedSeq(Person(name = "Bob", age = 25)),
1865
+ // 32 -> IndexedSeq(Person(name = "Alice", age = 32), Person(name = "Carol", age = 32))
1866
+ // )
1867
+ ```
1868
+
1869
+ #### `Chunk#groupMap` — Group and Map in One Pass
1870
+
1871
+ Group elements by a key and simultaneously transform the values:
1872
+
1873
+ ```scala
1874
+ trait Chunk[+A] {
1875
+ def groupMap[K, V](key: A => K)(f: A => V): Map[K, Chunk[V]]
1876
+ }
1877
+ ```
1878
+
1879
+ Grouping and mapping in a single operation can be more efficient than separate steps:
1880
+
1881
+ ```scala
1882
+ import zio.blocks.chunk.Chunk
1883
+
1884
+ val words = Chunk("apple", "banana", "cherry", "avocado")
1885
+ // words: Chunk[String] = IndexedSeq("apple", "banana", "cherry", "avocado")
1886
+ ```
1887
+
1888
+ Group words by their first letter and also uppercase them:
1889
+
1890
+ ```scala
1891
+ words.groupMap(_.head)(_.toUpperCase)
1892
+ // res116: Map[Char, Chunk[String]] = Map(
1893
+ // 'a' -> IndexedSeq("APPLE", "AVOCADO"),
1894
+ // 'b' -> IndexedSeq("BANANA"),
1895
+ // 'c' -> IndexedSeq("CHERRY")
1896
+ // )
1897
+ ```
1898
+
1899
+ #### `Chunk#grouped` — Partition into Fixed-Size Groups
1900
+
1901
+ Divide the chunk into groups of a fixed size, returning an iterator over subgroups:
1902
+
1903
+ ```scala
1904
+ trait Chunk[+A] {
1905
+ def grouped(size: Int): Iterator[Chunk[A]]
1906
+ }
1907
+ ```
1908
+
1909
+ Fixed-size grouping is useful for batch processing:
1910
+
1911
+ ```scala
1912
+ import zio.blocks.chunk.Chunk
1913
+
1914
+ val numbers = Chunk(1, 2, 3, 4, 5, 6, 7, 8)
1915
+ ```
1916
+
1917
+ Groups of size 3 yield two full groups and a partial:
1918
+
1919
+ ```scala
1920
+ numbers.grouped(3).toList
1921
+ // res118: List[Chunk[Int]] = List(
1922
+ // IndexedSeq(1, 2, 3),
1923
+ // IndexedSeq(4, 5, 6),
1924
+ // IndexedSeq(7, 8)
1925
+ // )
1926
+ ```
1927
+
1928
+ ### Equality and Iteration
1929
+
1930
+ Chunks support standard equality comparison and side-effect iteration:
1931
+
1932
+ #### `Chunk#equals` — Equality Comparison
1933
+
1934
+ Compare two chunks for equality:
1935
+
1936
+ ```scala
1937
+ trait Chunk[+A] {
1938
+ override def equals(that: Any): Boolean
1939
+ }
1940
+ ```
1941
+
1942
+ Chunks are equal if they contain the same elements in the same order. The comparison works correctly regardless of the internal representation (array-backed vs. tree-structured):
1943
+
1944
+ ```scala
1945
+ import zio.blocks.chunk.Chunk
1946
+
1947
+ val chunk1 = Chunk(1, 2, 3)
1948
+ // chunk1: Chunk[Int] = IndexedSeq(1, 2, 3)
1949
+ val chunk2 = Chunk(1, 2, 3)
1950
+ // chunk2: Chunk[Int] = IndexedSeq(1, 2, 3)
1951
+ val chunk3 = Chunk(1, 2, 4)
1952
+ // chunk3: Chunk[Int] = IndexedSeq(1, 2, 4)
1953
+
1954
+ chunk1 == chunk2
1955
+ // res120: Boolean = true
1956
+ chunk1 == chunk3
1957
+ // res121: Boolean = false
1958
+ ```
1959
+
1960
+ #### `Chunk#hashCode` — Hash Code Computation
1961
+
1962
+ Get the hash code of a chunk:
1963
+
1964
+ ```scala
1965
+ trait Chunk[+A] {
1966
+ override def hashCode: Int
1967
+ }
1968
+ ```
1969
+
1970
+ Chunks compute their hash code using the MurmurHash3 algorithm applied to all elements, similar to Scala's collection hashing. Equal chunks always have equal hash codes, making chunks suitable for use as keys in hash-based collections:
1971
+
1972
+ ```scala
1973
+ import zio.blocks.chunk.Chunk
1974
+
1975
+ val chunk1 = Chunk(1, 2, 3)
1976
+ // chunk1: Chunk[Int] = IndexedSeq(1, 2, 3)
1977
+ val chunk2 = Chunk(1, 2, 3)
1978
+ // chunk2: Chunk[Int] = IndexedSeq(1, 2, 3)
1979
+
1980
+ chunk1.hashCode == chunk2.hashCode
1981
+ // res123: Boolean = true
1982
+
1983
+ val chunkSet = Set(chunk1)
1984
+ // chunkSet: Set[Chunk[Int]] = Set(IndexedSeq(1, 2, 3))
1985
+ chunkSet.contains(chunk2)
1986
+ // res124: Boolean = true
1987
+ ```
1988
+
1989
+ Chunks can be reliably used in Maps and Sets because equal chunks have equal hash codes.
1990
+
1991
+ #### `Chunk#foreach` — Iteration with Side Effects
1992
+
1993
+ Perform a side effect for each element (used with `Unit`-returning functions):
1994
+
1995
+ ```scala
1996
+ trait Chunk[+A] {
1997
+ override def foreach[B](f: A => B): Unit
1998
+ }
1999
+ ```
2000
+
2001
+ `foreach` iterates through all elements and applies a function, ignoring the return value. This is useful for side effects like logging or printing:
2002
+
2003
+ ```scala
2004
+ import zio.blocks.chunk.Chunk
2005
+
2006
+ val chunk = Chunk(1, 2, 3)
2007
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
2008
+
2009
+ // Print each element
2010
+ chunk.foreach(n => println(s"Element: $n"))
2011
+ // Element: 1
2012
+ // Element: 2
2013
+ // Element: 3
2014
+ ```
2015
+
2016
+ ### Conversion
2017
+
2018
+ Sometimes you need to move data from Chunk into other Scala collections or represent it as text. These conversion operations make it easy to export your chunk into arrays, lists, sequences, or render it as a string for logging and display:
2019
+
2020
+ #### `Chunk#toArray` — To `Array`
2021
+
2022
+ Convert to an array:
2023
+
2024
+ ```scala
2025
+ trait Chunk[+A] {
2026
+ def toArray[A1 >: A: ClassTag]: Array[A1]
2027
+ }
2028
+ ```
2029
+
2030
+ Converting to an array materializes all elements:
2031
+
2032
+ ```scala
2033
+ import zio.blocks.chunk.Chunk
2034
+
2035
+ val chunk = Chunk(1, 2, 3, 4)
2036
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
2037
+ val array: Array[Int] = chunk.toArray
2038
+ // array: Array[Int] = Array(1, 2, 3, 4)
2039
+ ```
2040
+
2041
+ #### `Chunk#toList` — To `List`
2042
+
2043
+ Convert to a `List`:
2044
+
2045
+ ```scala
2046
+ trait Chunk[+A] {
2047
+ def toList: List[A]
2048
+ }
2049
+ ```
2050
+
2051
+ Converting to a list produces a sequential data structure:
2052
+
2053
+ ```scala
2054
+ import zio.blocks.chunk.Chunk
2055
+
2056
+ val chunk = Chunk("a", "b", "c")
2057
+ // chunk: Chunk[String] = IndexedSeq("a", "b", "c")
2058
+ val list = chunk.toList
2059
+ // list: List[String] = List("a", "b", "c")
2060
+ ```
2061
+
2062
+ #### `Chunk#toSeq`, `Chunk#toIterable`, `Chunk#toIndexedSeq` — Standard Collections
2063
+
2064
+ Convert to various Scala collection types:
2065
+
2066
+ ```scala
2067
+ trait Chunk[+A] {
2068
+ def toSeq: Seq[A]
2069
+ def toIterable: Iterable[A]
2070
+ def toIndexedSeq: IndexedSeq[A]
2071
+ }
2072
+ ```
2073
+
2074
+ Converting to standard Scala collections enables interop:
2075
+
2076
+ ```scala
2077
+ import zio.blocks.chunk.Chunk
2078
+
2079
+ val chunk = Chunk(1, 2, 3)
2080
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
2081
+
2082
+ chunk.toSeq
2083
+ // res130: Chunk[Int] = IndexedSeq(1, 2, 3)
2084
+ chunk.toIndexedSeq
2085
+ // res131: IndexedSeq[Int] = IndexedSeq(1, 2, 3)
2086
+ ```
2087
+
2088
+ #### `Chunk#toString` — String Representation
2089
+
2090
+ Convert to a string:
2091
+
2092
+ ```scala
2093
+ trait Chunk[+A] {
2094
+ def toString: String
2095
+ }
2096
+ ```
2097
+
2098
+ String representation shows all elements in a compact format:
2099
+
2100
+ ```scala
2101
+ import zio.blocks.chunk.Chunk
2102
+
2103
+ val chunk = Chunk(1, 2, 3)
2104
+ ```
2105
+
2106
+ String conversion shows the chunk contents:
2107
+
2108
+ ```scala
2109
+ chunk.toString
2110
+ // res133: String = "Chunk(1,2,3)"
2111
+ ```
2112
+
2113
+ #### `Chunk#toVector` — To `Vector`
2114
+
2115
+ Convert to a `Vector`:
2116
+
2117
+ ```scala
2118
+ trait Chunk[+A] {
2119
+ def toVector: Vector[A]
2120
+ }
2121
+ ```
2122
+
2123
+ Conversion to `Vector` produces an efficient indexed sequence:
2124
+
2125
+ ```scala
2126
+ import zio.blocks.chunk.Chunk
2127
+
2128
+ val chunk = Chunk(1, 2, 3)
2129
+ ```
2130
+
2131
+ ```scala
2132
+ chunk.toVector
2133
+ // res135: Vector[Int] = Vector(1, 2, 3)
2134
+ ```
2135
+
2136
+ ### Specialized Accessors for Primitive Types
2137
+
2138
+ For primitive types, direct accessors avoid boxing:
2139
+
2140
+ ```scala
2141
+ trait Chunk[+A] {
2142
+ def byte(index: Int)(implicit ev: A <:< Byte): Byte
2143
+ def boolean(index: Int)(implicit ev: A <:< Boolean): Boolean
2144
+ def char(index: Int)(implicit ev: A <:< Char): Char
2145
+ def double(index: Int)(implicit ev: A <:< Double): Double
2146
+ def float(index: Int)(implicit ev: A <:< Float): Float
2147
+ def int(index: Int)(implicit ev: A <:< Int): Int
2148
+ def long(index: Int)(implicit ev: A <:< Long): Long
2149
+ def short(index: Int)(implicit ev: A <:< Short): Short
2150
+ }
2151
+ ```
2152
+
2153
+ Accessing primitives without boxing demonstrates zero-overhead specialization:
2154
+
2155
+ ```scala
2156
+ import zio.blocks.chunk.Chunk
2157
+
2158
+ val bytes: Chunk[Byte] = Chunk(1.toByte, 2.toByte, 3.toByte)
2159
+ val ints = Chunk(10, 20, 30)
2160
+ ```
2161
+
2162
+ Accessing primitives by specialized methods avoids boxing:
2163
+
2164
+ ```scala
2165
+ bytes.byte(0)
2166
+ // res137: Byte = 1
2167
+ ints.int(1)
2168
+ // res138: Int = 20
2169
+ ```
2170
+
2171
+ ### Iterator Access
2172
+
2173
+ Access chunks through iterators for sequential processing or collect the known size of elements:
2174
+
2175
+ #### `Chunk#iterator` — Get a Standard Iterator
2176
+
2177
+ Get a standard Scala `Iterator` to traverse the chunk sequentially:
2178
+
2179
+ ```scala
2180
+ trait Chunk[+A] {
2181
+ def iterator: Iterator[A]
2182
+ }
2183
+ ```
2184
+
2185
+ The iterator is useful for sequential processing and integrates with Scala's collection ecosystem:
2186
+
2187
+ ```scala
2188
+ import zio.blocks.chunk.Chunk
2189
+
2190
+ val chunk = Chunk(1, 2, 3, 4, 5)
2191
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
2192
+ val iter = chunk.iterator
2193
+ // iter: Iterator[Int] = non-empty iterator
2194
+
2195
+ // Process elements sequentially
2196
+ iter.take(3).toList
2197
+ // res140: List[Int] = List(1, 2, 3)
2198
+ ```
2199
+
2200
+ #### `Chunk#chunkIterator` — Get a Chunk-Specific Iterator
2201
+
2202
+ Get a `Chunk.ChunkIterator` for more efficient traversal within the Chunk ecosystem:
2203
+
2204
+ ```scala
2205
+ trait Chunk[+A] {
2206
+ def chunkIterator: Chunk.ChunkIterator[A]
2207
+ }
2208
+ ```
2209
+
2210
+ `ChunkIterator` is optimized for Chunk operations and can be more efficient than the standard iterator in some cases:
2211
+
2212
+ ```scala
2213
+ import zio.blocks.chunk.Chunk
2214
+
2215
+ val chunk = Chunk(10, 20, 30, 40)
2216
+ // chunk: Chunk[Int] = IndexedSeq(10, 20, 30, 40)
2217
+ val chunkIter = chunk.chunkIterator
2218
+ // chunkIter: ChunkIterator[Int] = IndexedSeq(10, 20, 30, 40)
2219
+ ```
2220
+
2221
+ #### `Chunk#knownSize` — Get Iterator Size Hint
2222
+
2223
+ Get the known size of the chunk for iterator protocol compatibility:
2224
+
2225
+ ```scala
2226
+ trait Chunk[+A] {
2227
+ def knownSize: Int
2228
+ }
2229
+ ```
2230
+
2231
+ The known size helps collection builders optimize allocations:
2232
+
2233
+ ```scala
2234
+ import zio.blocks.chunk.Chunk
2235
+
2236
+ val chunk = Chunk(1, 2, 3)
2237
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
2238
+ val size = chunk.knownSize
2239
+ // size: Int = 3
2240
+ ```
2241
+
2242
+ ### Array Operations
2243
+
2244
+ Perform bulk operations with arrays for interoperability with imperative code:
2245
+
2246
+ #### `Chunk#copyToArray` — Copy to Destination Array
2247
+
2248
+ Copy elements from the chunk to a destination array at a specified position:
2249
+
2250
+ ```scala
2251
+ trait Chunk[+A] {
2252
+ def copyToArray[B >: A](dest: Array[B], destPos: Int, length: Int): Int
2253
+ }
2254
+ ```
2255
+
2256
+ This is useful for efficient bulk export of chunk contents to arrays, particularly in interop scenarios. The method returns the number of elements actually copied:
2257
+
2258
+ ```scala
2259
+ import zio.blocks.chunk.Chunk
2260
+
2261
+ val chunk = Chunk(1, 2, 3, 4, 5)
2262
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
2263
+ val dest = new Array[Int](10)
2264
+ // dest: Array[Int] = Array(0, 0, 1, 2, 3, 0, 0, 0, 0, 0)
2265
+
2266
+ val numCopied = chunk.copyToArray(dest, 2, 3)
2267
+ // numCopied: Int = 3
2268
+ dest.take(5)
2269
+ // res144: Array[Int] = Array(0, 0, 1, 2, 3)
2270
+ ```
2271
+
2272
+ ### Materialization and Optimization
2273
+
2274
+ When you build chunks through many concatenations, they internally form a tree structure for efficiency. However, if you plan to access elements many times, materialization converts that tree into a flat, optimized array-backed representation:
2275
+
2276
+ Force the chunk to an array-backed representation, eliminating lazy concatenation trees. Useful before performing many operations:
2277
+
2278
+ ```scala
2279
+ trait Chunk[+A] {
2280
+ def materialize[A1 >: A]: Chunk[A1]
2281
+ }
2282
+ ```
2283
+
2284
+ Materializing a tree-built chunk converts it to an efficient array-backed form:
2285
+
2286
+ ```scala
2287
+ import zio.blocks.chunk.Chunk
2288
+
2289
+ // Build through many concatenations (creates Concat tree)
2290
+ val built = (1 to 100).foldLeft(Chunk.empty[Int]) { (acc, i) =>
2291
+ acc ++ Chunk(i)
2292
+ }
2293
+ // built: Chunk[Int] = IndexedSeq(
2294
+ // 1,
2295
+ // 2,
2296
+ // 3,
2297
+ // 4,
2298
+ // 5,
2299
+ // 6,
2300
+ // 7,
2301
+ // 8,
2302
+ // 9,
2303
+ // 10,
2304
+ // 11,
2305
+ // 12,
2306
+ // 13,
2307
+ // 14,
2308
+ // 15,
2309
+ // 16,
2310
+ // 17,
2311
+ // 18,
2312
+ // 19,
2313
+ // 20,
2314
+ // 21,
2315
+ // 22,
2316
+ // 23,
2317
+ // 24,
2318
+ // 25,
2319
+ // 26,
2320
+ // 27,
2321
+ // 28,
2322
+ // 29,
2323
+ // 30,
2324
+ // 31,
2325
+ // 32,
2326
+ // 33,
2327
+ // 34,
2328
+ // 35,
2329
+ // 36,
2330
+ // 37,
2331
+ // 38,
2332
+ // 39,
2333
+ // 40,
2334
+ // 41,
2335
+ // 42,
2336
+ // 43,
2337
+ // 44,
2338
+ // 45,
2339
+ // 46,
2340
+ // 47,
2341
+ // 48,
2342
+ // ...
2343
+
2344
+ // Materialize to array-backed representation
2345
+ val materialized = built.materialize
2346
+ // materialized: Chunk[Int] = IndexedSeq(
2347
+ // 1,
2348
+ // 2,
2349
+ // 3,
2350
+ // 4,
2351
+ // 5,
2352
+ // 6,
2353
+ // 7,
2354
+ // 8,
2355
+ // 9,
2356
+ // 10,
2357
+ // 11,
2358
+ // 12,
2359
+ // 13,
2360
+ // 14,
2361
+ // 15,
2362
+ // 16,
2363
+ // 17,
2364
+ // 18,
2365
+ // 19,
2366
+ // 20,
2367
+ // 21,
2368
+ // 22,
2369
+ // 23,
2370
+ // 24,
2371
+ // 25,
2372
+ // 26,
2373
+ // 27,
2374
+ // 28,
2375
+ // 29,
2376
+ // 30,
2377
+ // 31,
2378
+ // 32,
2379
+ // 33,
2380
+ // 34,
2381
+ // 35,
2382
+ // 36,
2383
+ // 37,
2384
+ // 38,
2385
+ // 39,
2386
+ // 40,
2387
+ // 41,
2388
+ // 42,
2389
+ // 43,
2390
+ // 44,
2391
+ // 45,
2392
+ // 46,
2393
+ // 47,
2394
+ // 48,
2395
+ // ...
2396
+ // Now faster for repeated access or further operations
2397
+ ```
2398
+
2399
+ ## Advanced Usage
2400
+
2401
+ Advanced use cases include bit-level operations, working with specialized chunk types, and comparing Chunk with other data structures:
2402
+
2403
+ ### Bit Operations
2404
+
2405
+ Chunk provides specialized operations to work at the bit level with numeric types. These are useful when you need to inspect the binary representation of bytes, integers, or longs while respecting endianness preferences:
2406
+
2407
+ #### `Chunk#asBitsByte` — Convert to Byte Bits
2408
+
2409
+ Convert a chunk of bytes into a chunk of individual bits. This method is only available on `Chunk[Byte]` through an implicit constraint:
2410
+
2411
+ ```scala
2412
+ trait Chunk[+A] {
2413
+ def asBitsByte(implicit ev: A <:< Byte): Chunk[Boolean]
2414
+ }
2415
+ ```
2416
+
2417
+ Converting bytes to bits reveals the binary representation:
2418
+
2419
+ ```scala
2420
+ import zio.blocks.chunk.Chunk
2421
+
2422
+ val bytes = Chunk(1.toByte, 2.toByte)
2423
+ // bytes: Chunk[Byte] = IndexedSeq(1, 2)
2424
+ val bits = bytes.asBitsByte
2425
+ // bits: Chunk[Boolean] = IndexedSeq(
2426
+ // false,
2427
+ // false,
2428
+ // false,
2429
+ // false,
2430
+ // false,
2431
+ // false,
2432
+ // false,
2433
+ // true,
2434
+ // false,
2435
+ // false,
2436
+ // false,
2437
+ // false,
2438
+ // false,
2439
+ // false,
2440
+ // true,
2441
+ // false
2442
+ // )
2443
+ ```
2444
+
2445
+ #### `Chunk#asBitsInt` — Convert to Int Bits
2446
+
2447
+ Convert a chunk of ints into a chunk of individual bits with a specified endianness. This method is only available on `Chunk[Int]` through an implicit constraint:
2448
+
2449
+ ```scala
2450
+ trait Chunk[+A] {
2451
+ def asBitsInt(endianness: Chunk.BitChunk.Endianness)(implicit ev: A <:< Int): Chunk[Boolean]
2452
+ }
2453
+ ```
2454
+
2455
+ Converting integers to bits with endianness control provides bit-level access:
2456
+
2457
+ ```scala
2458
+ import zio.blocks.chunk.Chunk
2459
+
2460
+ val ints = Chunk(1, 2, 3)
2461
+ // ints: Chunk[Int] = IndexedSeq(1, 2, 3)
2462
+ val bits = ints.asBitsInt(Chunk.BitChunk.Endianness.BigEndian)
2463
+ // bits: Chunk[Boolean] = IndexedSeq(
2464
+ // false,
2465
+ // false,
2466
+ // false,
2467
+ // false,
2468
+ // false,
2469
+ // false,
2470
+ // false,
2471
+ // false,
2472
+ // false,
2473
+ // false,
2474
+ // false,
2475
+ // false,
2476
+ // false,
2477
+ // false,
2478
+ // false,
2479
+ // false,
2480
+ // false,
2481
+ // false,
2482
+ // false,
2483
+ // false,
2484
+ // false,
2485
+ // false,
2486
+ // false,
2487
+ // false,
2488
+ // false,
2489
+ // false,
2490
+ // false,
2491
+ // false,
2492
+ // false,
2493
+ // false,
2494
+ // false,
2495
+ // true,
2496
+ // false,
2497
+ // false,
2498
+ // false,
2499
+ // false,
2500
+ // false,
2501
+ // false,
2502
+ // false,
2503
+ // false,
2504
+ // false,
2505
+ // false,
2506
+ // false,
2507
+ // false,
2508
+ // false,
2509
+ // false,
2510
+ // false,
2511
+ // false,
2512
+ // ...
2513
+ ```
2514
+
2515
+ #### `Chunk#asBitsLong` — Convert to Long Bits
2516
+
2517
+ Convert a chunk of longs into a chunk of individual bits with a specified endianness. This method is only available on `Chunk[Long]` through an implicit constraint:
2518
+
2519
+ ```scala
2520
+ trait Chunk[+A] {
2521
+ def asBitsLong(endianness: Chunk.BitChunk.Endianness)(implicit ev: A <:< Long): Chunk[Boolean]
2522
+ }
2523
+ ```
2524
+
2525
+ Converting longs to bits supports both big-endian and little-endian representations:
2526
+
2527
+ ```scala
2528
+ import zio.blocks.chunk.Chunk
2529
+
2530
+ val longs = Chunk(1L, 2L, 3L)
2531
+ // longs: Chunk[Long] = IndexedSeq(1L, 2L, 3L)
2532
+ val bits = longs.asBitsLong(Chunk.BitChunk.Endianness.LittleEndian)
2533
+ // bits: Chunk[Boolean] = IndexedSeq(
2534
+ // true,
2535
+ // false,
2536
+ // false,
2537
+ // false,
2538
+ // false,
2539
+ // false,
2540
+ // false,
2541
+ // false,
2542
+ // false,
2543
+ // false,
2544
+ // false,
2545
+ // false,
2546
+ // false,
2547
+ // false,
2548
+ // false,
2549
+ // false,
2550
+ // false,
2551
+ // false,
2552
+ // false,
2553
+ // false,
2554
+ // false,
2555
+ // false,
2556
+ // false,
2557
+ // false,
2558
+ // false,
2559
+ // false,
2560
+ // false,
2561
+ // false,
2562
+ // false,
2563
+ // false,
2564
+ // false,
2565
+ // false,
2566
+ // false,
2567
+ // false,
2568
+ // false,
2569
+ // false,
2570
+ // false,
2571
+ // false,
2572
+ // false,
2573
+ // false,
2574
+ // false,
2575
+ // false,
2576
+ // false,
2577
+ // false,
2578
+ // false,
2579
+ // false,
2580
+ // false,
2581
+ // false,
2582
+ // ...
2583
+ ```
2584
+
2585
+ #### `Chunk#toBinaryString` — Convert Boolean Chunk to Binary String
2586
+
2587
+ Convert a chunk of booleans (representing bits) into a binary string representation:
2588
+
2589
+ ```scala
2590
+ trait Chunk[+A] {
2591
+ def toBinaryString(implicit ev: A <:< Boolean): String
2592
+ }
2593
+ ```
2594
+
2595
+ This operation yields a string of '0' and '1' characters corresponding to the bit values:
2596
+
2597
+ ```scala
2598
+ import zio.blocks.chunk.Chunk
2599
+
2600
+ val bits = Chunk(true, false, true, false)
2601
+ ```
2602
+
2603
+ The binary string shows the sequence of bits:
2604
+
2605
+ ```scala
2606
+ bits.toBinaryString
2607
+ // res150: String = "1010"
2608
+ ```
2609
+
2610
+ #### `Chunk#toPackedByte` — Pack Bits into Bytes
2611
+
2612
+ Pack a chunk of booleans (representing individual bits) into a packed byte representation:
2613
+
2614
+ ```scala
2615
+ trait Chunk[+A] {
2616
+ def toPackedByte(implicit ev: A <:< Boolean): Chunk[Byte]
2617
+ }
2618
+ ```
2619
+
2620
+ This is useful when you need a space-efficient representation of individual bits by packing them eight per byte. The bits are packed from left to right, with the first bit becoming the most significant bit of the first byte:
2621
+
2622
+ ```scala
2623
+ import zio.blocks.chunk.Chunk
2624
+
2625
+ val bits = Chunk(true, false, true, false, true, false, true, false)
2626
+ // bits: Chunk[Boolean] = IndexedSeq(
2627
+ // true,
2628
+ // false,
2629
+ // true,
2630
+ // false,
2631
+ // true,
2632
+ // false,
2633
+ // true,
2634
+ // false
2635
+ // )
2636
+ val packed = bits.toPackedByte
2637
+ // packed: Chunk[Byte] = IndexedSeq(-86)
2638
+ ```
2639
+
2640
+ #### `Chunk#toPackedInt` — Pack Bits into Integers
2641
+
2642
+ Pack a chunk of booleans into a packed integer representation with specified endianness:
2643
+
2644
+ ```scala
2645
+ trait Chunk[+A] {
2646
+ def toPackedInt(endianness: Chunk.BitChunk.Endianness)(implicit ev: A <:< Boolean): Chunk[Int]
2647
+ }
2648
+ ```
2649
+
2650
+ Packing bits into integers is efficient for storing large bit arrays. The endianness parameter controls how bits are ordered within each integer:
2651
+
2652
+ ```scala
2653
+ import zio.blocks.chunk.Chunk
2654
+
2655
+ val bits = Chunk(true, false, true, false, true, false, true, false)
2656
+ // bits: Chunk[Boolean] = IndexedSeq(
2657
+ // true,
2658
+ // false,
2659
+ // true,
2660
+ // false,
2661
+ // true,
2662
+ // false,
2663
+ // true,
2664
+ // false
2665
+ // )
2666
+ val packed = bits.toPackedInt(Chunk.BitChunk.Endianness.BigEndian)
2667
+ // packed: Chunk[Int] = IndexedSeq(170)
2668
+ ```
2669
+
2670
+ #### `Chunk#toPackedLong` — Pack Bits into Longs
2671
+
2672
+ Pack a chunk of booleans into a packed long representation with specified endianness:
2673
+
2674
+ ```scala
2675
+ trait Chunk[+A] {
2676
+ def toPackedLong(endianness: Chunk.BitChunk.Endianness)(implicit ev: A <:< Boolean): Chunk[Long]
2677
+ }
2678
+ ```
2679
+
2680
+ Packing bits into longs is the most space-efficient approach for bit storage, packing 64 bits per long. Choose the endianness that matches your serialization format:
2681
+
2682
+ ```scala
2683
+ import zio.blocks.chunk.Chunk
2684
+
2685
+ val bits = Chunk(true, false, true, false, true, false, true, false)
2686
+ // bits: Chunk[Boolean] = IndexedSeq(
2687
+ // true,
2688
+ // false,
2689
+ // true,
2690
+ // false,
2691
+ // true,
2692
+ // false,
2693
+ // true,
2694
+ // false
2695
+ // )
2696
+ val packed = bits.toPackedLong(Chunk.BitChunk.Endianness.LittleEndian)
2697
+ // packed: Chunk[Long] = IndexedSeq(6124895493223874560L)
2698
+ ```
2699
+
2700
+ #### `Chunk#&` — Bitwise AND
2701
+
2702
+ Perform bitwise AND between two boolean chunks:
2703
+
2704
+ ```scala
2705
+ trait Chunk[+A] {
2706
+ def &(that: Chunk[Boolean])(implicit ev: A <:< Boolean): Chunk.BitChunkByte
2707
+ }
2708
+ ```
2709
+
2710
+ Bitwise AND operates on boolean chunks, returning a `BitChunkByte` representing the packed result:
2711
+
2712
+ ```scala
2713
+ import zio.blocks.chunk.Chunk
2714
+
2715
+ val bits1 = Chunk(true, true, false, false)
2716
+ // bits1: Chunk[Boolean] = IndexedSeq(true, true, false, false)
2717
+ val bits2 = Chunk(true, false, true, false)
2718
+ // bits2: Chunk[Boolean] = IndexedSeq(true, false, true, false)
2719
+ val result = bits1 & bits2
2720
+ // result: BitChunkByte = IndexedSeq(true, false, false, false)
2721
+ ```
2722
+
2723
+ #### `Chunk#|` — Bitwise OR
2724
+
2725
+ Perform bitwise OR between two boolean chunks:
2726
+
2727
+ ```scala
2728
+ trait Chunk[+A] {
2729
+ def |(that: Chunk[Boolean])(implicit ev: A <:< Boolean): Chunk.BitChunkByte
2730
+ }
2731
+ ```
2732
+
2733
+ Bitwise OR combines boolean chunks with an OR operation:
2734
+
2735
+ ```scala
2736
+ import zio.blocks.chunk.Chunk
2737
+
2738
+ val bits1 = Chunk(true, false, false, false)
2739
+ // bits1: Chunk[Boolean] = IndexedSeq(true, false, false, false)
2740
+ val bits2 = Chunk(false, true, false, false)
2741
+ // bits2: Chunk[Boolean] = IndexedSeq(false, true, false, false)
2742
+ val result = bits1 | bits2
2743
+ // result: BitChunkByte = IndexedSeq(true, true, false, false)
2744
+ ```
2745
+
2746
+ #### `Chunk#^` — Bitwise XOR
2747
+
2748
+ Perform bitwise XOR between two boolean chunks:
2749
+
2750
+ ```scala
2751
+ trait Chunk[+A] {
2752
+ def ^(that: Chunk[Boolean])(implicit ev: A <:< Boolean): Chunk.BitChunkByte
2753
+ }
2754
+ ```
2755
+
2756
+ Bitwise XOR returns true when bits differ:
2757
+
2758
+ ```scala
2759
+ import zio.blocks.chunk.Chunk
2760
+
2761
+ val bits1 = Chunk(true, false, true, false)
2762
+ // bits1: Chunk[Boolean] = IndexedSeq(true, false, true, false)
2763
+ val bits2 = Chunk(true, true, false, false)
2764
+ // bits2: Chunk[Boolean] = IndexedSeq(true, true, false, false)
2765
+ val result = bits1 ^ bits2
2766
+ // result: BitChunkByte = IndexedSeq(false, true, true, false)
2767
+ ```
2768
+
2769
+ #### `Chunk#negate` — Bitwise NOT
2770
+
2771
+ Perform bitwise NOT (negation) on a boolean chunk:
2772
+
2773
+ ```scala
2774
+ trait Chunk[+A] {
2775
+ def negate(implicit ev: A <:< Boolean): Chunk.BitChunkByte
2776
+ }
2777
+ ```
2778
+
2779
+ Bitwise NOT inverts all bits in the chunk:
2780
+
2781
+ ```scala
2782
+ import zio.blocks.chunk.Chunk
2783
+
2784
+ val bits = Chunk(true, false, true, false)
2785
+ // bits: Chunk[Boolean] = IndexedSeq(true, false, true, false)
2786
+ val inverted = bits.negate
2787
+ // inverted: BitChunkByte = IndexedSeq(false, true, false, true)
2788
+ ```
2789
+
2790
+ ### Text Operations
2791
+
2792
+ Chunk makes it convenient to work with text by converting byte or character chunks into readable strings. You can also encode chunks as Base64 for safe transmission or storage in text-based formats:
2793
+
2794
+ #### `Chunk#asString` — Convert to String
2795
+
2796
+ Convert a chunk of bytes or characters into a string. Supports `Chunk[Byte]`, `Chunk[Char]`, and `Chunk[String]`:
2797
+
2798
+ ```scala
2799
+ import zio.blocks.chunk.Chunk
2800
+
2801
+ val chars = Chunk('H', 'i')
2802
+ val bytes = Chunk(72.toByte, 105.toByte)
2803
+ ```
2804
+
2805
+ Converting byte and character chunks to strings:
2806
+
2807
+ ```scala
2808
+ val str = chars.asString
2809
+ // str: String = "Hi"
2810
+ val strFromBytes = bytes.asString
2811
+ // strFromBytes: String = "Hi"
2812
+ ```
2813
+
2814
+ #### `Chunk#asString(charset)` — Convert to String with Charset
2815
+
2816
+ Convert a chunk of bytes into a string using a specified character encoding:
2817
+
2818
+ ```scala
2819
+ trait Chunk[+A] {
2820
+ def asString(charset: Charset)(implicit ev: A <:< Byte): String
2821
+ }
2822
+ ```
2823
+
2824
+ When working with byte chunks from external sources, you may need to specify the character encoding. Use UTF-8 for most cases, but other encodings are available:
2825
+
2826
+ ```scala
2827
+ import zio.blocks.chunk.Chunk
2828
+ import java.nio.charset.StandardCharsets
2829
+
2830
+ val utf8Bytes = Chunk(72.toByte, 101.toByte, 108.toByte, 108.toByte, 111.toByte)
2831
+ // utf8Bytes: Chunk[Byte] = IndexedSeq(72, 101, 108, 108, 111)
2832
+ val stringUTF8 = utf8Bytes.asString(StandardCharsets.UTF_8)
2833
+ // stringUTF8: String = "Hello"
2834
+ ```
2835
+
2836
+ #### `Chunk#asBase64String` — Encode as Base64
2837
+
2838
+ Convert a chunk of bytes or characters to a Base64-encoded string:
2839
+
2840
+ ```scala
2841
+ import zio.blocks.chunk.Chunk
2842
+
2843
+ val bytes = Chunk(1.toByte, 2.toByte, 3.toByte)
2844
+ // bytes: Chunk[Byte] = IndexedSeq(1, 2, 3)
2845
+ val base64 = bytes.asBase64String
2846
+ // base64: String = "AQID"
2847
+ ```
2848
+
2849
+ ### Advanced Transformations
2850
+
2851
+ Chunk supports powerful transformation patterns for complex use cases. Collect elements while a condition holds, partition results into success and failure paths using Either, and attach indices to track element positions:
2852
+
2853
+ #### `Chunk#collectWhile` — Collect with Early Exit
2854
+
2855
+ Apply a partial function while a condition holds, stopping at the first non-match:
2856
+
2857
+ ```scala
2858
+ import zio.blocks.chunk.Chunk
2859
+
2860
+ val values: Chunk[Any] = Chunk(1, 2, "x", 3, 4)
2861
+ // values: Chunk[Any] = IndexedSeq(1, 2, "x", 3, 4)
2862
+ val collected = values.collectWhile { case n: Int => n * 10 }
2863
+ // collected: Chunk[Int] = IndexedSeq(10, 20)
2864
+ ```
2865
+
2866
+ #### `Chunk#partitionMap` — Partition into Two Chunks by Either Result
2867
+
2868
+ Partition elements by applying a total function that returns `Either`:
2869
+
2870
+ ```scala
2871
+ trait Chunk[+A] {
2872
+ def partitionMap[B, C](f: A => Either[B, C]): (Chunk[B], Chunk[C])
2873
+ }
2874
+ ```
2875
+
2876
+ Partitioning elements by an Either result separates success and failure cases:
2877
+
2878
+ ```scala
2879
+ import zio.blocks.chunk.Chunk
2880
+
2881
+ val numbers = Chunk(1, 2, 3, 4, 5)
2882
+ // numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
2883
+ val (evens, odds) = numbers.partitionMap { n =>
2884
+ if (n % 2 == 0) Left(n) else Right(n)
2885
+ }
2886
+ // evens: Chunk[Int] = IndexedSeq(2, 4)
2887
+ // odds: Chunk[Int] = IndexedSeq(1, 3, 5)
2888
+ ```
2889
+
2890
+ #### `Chunk#zipWithIndexFrom` — Zip with Index Starting at Custom Value
2891
+
2892
+ Zip elements with indices starting from a custom value:
2893
+
2894
+ ```scala
2895
+ import zio.blocks.chunk.Chunk
2896
+
2897
+ val chunk = Chunk("a", "b", "c")
2898
+ ```
2899
+
2900
+ Zipping with a custom starting index pairs each element:
2901
+
2902
+ ```scala
2903
+ chunk.zipWithIndexFrom(10)
2904
+ // res164: Chunk[Tuple2[String, Int]] = IndexedSeq(
2905
+ // ("a", 10),
2906
+ // ("b", 11),
2907
+ // ("c", 12)
2908
+ // )
2909
+ ```
2910
+
2911
+ ### Safe Operations
2912
+
2913
+ Chunks can be empty, and these operations help you handle that case gracefully. Rather than throwing exceptions or returning null, these methods provide type-safe fallbacks and alternatives when working with potentially empty chunks:
2914
+
2915
+ Return the chunk if non-empty, otherwise return an alternative:
2916
+
2917
+ ```scala
2918
+ trait Chunk[+A] {
2919
+ def nonEmptyOrElse[B](ifEmpty: => B)(fn: NonEmptyChunk[A] => B): B
2920
+ }
2921
+ ```
2922
+
2923
+ Safely handling empty and non-empty cases provides type-safe alternatives:
2924
+
2925
+ ```scala
2926
+ import zio.blocks.chunk.{Chunk, NonEmptyChunk}
2927
+
2928
+ val chunk = Chunk(1, 2, 3)
2929
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
2930
+ val nonEmpty = NonEmptyChunk(1, 2, 3)
2931
+ // nonEmpty: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3)
2932
+ val result = nonEmpty.nonEmptyOrElse(Chunk.empty[Int])(identity)
2933
+ // result: Chunk[Int] | NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3)
2934
+
2935
+ val empty = Chunk.empty[Int]
2936
+ // empty: Chunk[Int] = IndexedSeq()
2937
+ val result2 = empty match {
2938
+ case c if c.isEmpty => Chunk(99)
2939
+ case c => c
2940
+ }
2941
+ // result2: Chunk[Int] = IndexedSeq(99)
2942
+ ```
2943
+
2944
+ ## NonEmptyChunk
2945
+
2946
+ `NonEmptyChunk[A]` is a **type-safe wrapper** around `Chunk[A]` that **guarantees the chunk is non-empty**. It provides the same operations as `Chunk` but with methods like `head` and `last` returning `A` directly instead of `Option[A]` or throwing an exception. This eliminates the need for runtime checks on common operations and makes empty-case handling explicit at the type level.
2947
+
2948
+ `NonEmptyChunk[A]`:
2949
+ - Is a purely functional, immutable sequence of at least one element
2950
+ - Provides type-safe access to first and last elements
2951
+ - Supports all Chunk operations while maintaining the non-empty guarantee
2952
+ - Allows safe reduction operations without requiring a default value
2953
+ - Integrates seamlessly with Chunk for interoperability
2954
+
2955
+ ### Construction
2956
+
2957
+ Create a `NonEmptyChunk` directly using varargs syntax:
2958
+
2959
+ ```scala
2960
+ object NonEmptyChunk {
2961
+ def apply[A](a: A, as: A*): NonEmptyChunk[A]
2962
+ }
2963
+ ```
2964
+
2965
+ Creating a non-empty chunk with direct constructor:
2966
+
2967
+ ```scala
2968
+ import zio.blocks.chunk.NonEmptyChunk
2969
+
2970
+ val nonEmpty = NonEmptyChunk(1, 2, 3)
2971
+ // nonEmpty: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3)
2972
+ ```
2973
+
2974
+ Create a `NonEmptyChunk` from an existing `Chunk` using `fromChunk`, which returns `Option[NonEmptyChunk[A]]`:
2975
+
2976
+ ```scala
2977
+ import zio.blocks.chunk.{Chunk, NonEmptyChunk}
2978
+
2979
+ val chunk = Chunk(1, 2, 3)
2980
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
2981
+ val maybeNonEmpty: Option[NonEmptyChunk[Int]] = NonEmptyChunk.fromChunk(chunk)
2982
+ // maybeNonEmpty: Option[NonEmptyChunk[Int]] = Some(NonEmptyChunk(1, 2, 3))
2983
+
2984
+ val empty = Chunk.empty[Int]
2985
+ // empty: Chunk[Int] = IndexedSeq()
2986
+ val nothingHere: Option[NonEmptyChunk[Int]] = NonEmptyChunk.fromChunk(empty)
2987
+ // nothingHere: Option[NonEmptyChunk[Int]] = None
2988
+ ```
2989
+
2990
+ Create from a Scala cons list using `fromCons` (requires a non-empty cons list):
2991
+
2992
+ ```scala
2993
+ import zio.blocks.chunk.NonEmptyChunk
2994
+
2995
+ val list = 1 :: 2 :: 3 :: Nil
2996
+ // list: List[Int] = List(1, 2, 3)
2997
+ val nonEmpty = list match {
2998
+ case cons: scala.collection.immutable.::[Int] => NonEmptyChunk.fromCons(cons)
2999
+ case _ => throw new IllegalArgumentException("list must be non-empty")
3000
+ }
3001
+ // nonEmpty: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3)
3002
+ ```
3003
+
3004
+ Create from an iterable with at least one guaranteed element using `fromIterable`:
3005
+
3006
+ ```scala
3007
+ import zio.blocks.chunk.NonEmptyChunk
3008
+
3009
+ val nonEmpty = NonEmptyChunk.fromIterable(5, List(1, 2, 3, 4))
3010
+ // nonEmpty: NonEmptyChunk[Int] = NonEmptyChunk(5, 1, 2, 3, 4)
3011
+ ```
3012
+
3013
+ ### Safe Access to Endpoints
3014
+
3015
+ Access the first and last elements without risk of exception:
3016
+
3017
+ ```scala
3018
+ trait NonEmptyChunk[+A] {
3019
+ def head: A
3020
+ def last: A
3021
+ }
3022
+ ```
3023
+
3024
+ Safely access endpoints of a guaranteed non-empty chunk:
3025
+
3026
+ ```scala
3027
+ import zio.blocks.chunk.NonEmptyChunk
3028
+
3029
+ val chunk = NonEmptyChunk(10, 20, 30, 40)
3030
+ // chunk: NonEmptyChunk[Int] = NonEmptyChunk(10, 20, 30, 40)
3031
+
3032
+ chunk.head
3033
+ // res171: Int = 10
3034
+ chunk.last
3035
+ // res172: Int = 40
3036
+ chunk.size
3037
+ // res173: Int = 4
3038
+ ```
3039
+
3040
+ ### Reduction and Aggregation
3041
+
3042
+ Reduce a non-empty chunk without requiring a starting value, since the chunk is guaranteed to have at least one element:
3043
+
3044
+ ```scala
3045
+ trait NonEmptyChunk[+A] {
3046
+ def reduce[B >: A](op: (B, B) => B): B
3047
+ def reduceMapLeft[B](map: A => B)(reduce: (B, A) => B): B
3048
+ def reduceMapRight[B](map: A => B)(reduce: (A, B) => B): B
3049
+ }
3050
+ ```
3051
+
3052
+ Reduction operations always produce a value (no Optional needed):
3053
+
3054
+ ```scala
3055
+ import zio.blocks.chunk.NonEmptyChunk
3056
+
3057
+ val numbers = NonEmptyChunk(1, 2, 3, 4, 5)
3058
+ // numbers: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3, 4, 5)
3059
+
3060
+ val sum = numbers.reduce(_ + _)
3061
+ // sum: Int = 15
3062
+
3063
+ val product = numbers.reduceMapLeft[Int](identity)(_ * _)
3064
+ // product: Int = 120
3065
+ ```
3066
+
3067
+ ### Transformations
3068
+
3069
+ Map and transform elements while preserving the non-empty guarantee:
3070
+
3071
+ ```scala
3072
+ trait NonEmptyChunk[+A] {
3073
+ def map[B](f: A => B): NonEmptyChunk[B]
3074
+ def flatMap[B](f: A => NonEmptyChunk[B]): NonEmptyChunk[B]
3075
+ def flatten[B](implicit ev: A <:< NonEmptyChunk[B]): NonEmptyChunk[B]
3076
+ def sorted[B >: A](implicit ord: Ordering[B]): NonEmptyChunk[B]
3077
+ def sortBy[B](f: A => B)(implicit ord: Ordering[B]): NonEmptyChunk[A]
3078
+ def distinct: NonEmptyChunk[A]
3079
+ def reverse: NonEmptyChunk[A]
3080
+ }
3081
+ ```
3082
+
3083
+ Transformations maintain the non-empty property for map, flatMap, and other structure-preserving operations:
3084
+
3085
+ ```scala
3086
+ import zio.blocks.chunk.NonEmptyChunk
3087
+
3088
+ val numbers = NonEmptyChunk(3, 1, 4, 1, 5)
3089
+ // numbers: NonEmptyChunk[Int] = NonEmptyChunk(3, 1, 4, 1, 5)
3090
+
3091
+ val doubled = numbers.map(_ * 2)
3092
+ // doubled: NonEmptyChunk[Int] = NonEmptyChunk(6, 2, 8, 2, 10)
3093
+
3094
+ val sorted = numbers.sorted
3095
+ // sorted: NonEmptyChunk[Int] = NonEmptyChunk(1, 1, 3, 4, 5)
3096
+ ```
3097
+
3098
+ ### Grouping and Aggregation
3099
+
3100
+ Group elements while maintaining non-empty chunks in each group:
3101
+
3102
+ ```scala
3103
+ trait NonEmptyChunk[+A] {
3104
+ def groupBy[K](f: A => K): Map[K, NonEmptyChunk[A]]
3105
+ def groupMap[K, V](key: A => K)(f: A => V): Map[K, NonEmptyChunk[V]]
3106
+ def grouped(size: Int): Iterator[NonEmptyChunk[A]]
3107
+ }
3108
+ ```
3109
+
3110
+ Grouping operations guarantee non-empty result chunks:
3111
+
3112
+ ```scala
3113
+ import zio.blocks.chunk.NonEmptyChunk
3114
+
3115
+ case class Person(name: String, age: Int)
3116
+ val people = NonEmptyChunk(Person("Alice", 30), Person("Bob", 25), Person("Carol", 30))
3117
+ // people: NonEmptyChunk[Person] = NonEmptyChunk(Person(Alice,30), Person(Bob,25), Person(Carol,30))
3118
+
3119
+ people.groupBy(_.age)
3120
+ // res177: Map[Int, NonEmptyChunk[Person]] = Map(
3121
+ // 25 -> NonEmptyChunk(Person(Bob,25)),
3122
+ // 30 -> NonEmptyChunk(Person(Alice,30), Person(Carol,30))
3123
+ // )
3124
+ ```
3125
+
3126
+ ### Combining and Concatenating
3127
+
3128
+ Extend a non-empty chunk with additional elements or other chunks:
3129
+
3130
+ ```scala
3131
+ trait NonEmptyChunk[+A] {
3132
+ def appended[A1 >: A](a: A1): NonEmptyChunk[A1]
3133
+ def prepended[A1 >: A](a: A1): NonEmptyChunk[A1]
3134
+ def ++[A1 >: A](that: Chunk[A1]): NonEmptyChunk[A1]
3135
+ def :+[A1 >: A](a: A1): NonEmptyChunk[A1] // alias for appended
3136
+ def +:[A1 >: A](a: A1): NonEmptyChunk[A1] // alias for prepended
3137
+ }
3138
+ ```
3139
+
3140
+ Concatenation operations preserve the non-empty guarantee:
3141
+
3142
+ ```scala
3143
+ import zio.blocks.chunk.{Chunk, NonEmptyChunk}
3144
+
3145
+ val nonEmpty = NonEmptyChunk(1, 2, 3)
3146
+ // nonEmpty: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3)
3147
+ val chunk = Chunk(4, 5, 6)
3148
+ // chunk: Chunk[Int] = IndexedSeq(4, 5, 6)
3149
+
3150
+ val appended = nonEmpty :+ 4
3151
+ // appended: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3, 4)
3152
+
3153
+ val concatenated = nonEmpty ++ chunk
3154
+ // concatenated: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3, 4, 5, 6)
3155
+
3156
+ val prepended = 0 +: nonEmpty
3157
+ // prepended: NonEmptyChunk[Int] = NonEmptyChunk(0, 1, 2, 3)
3158
+ ```
3159
+
3160
+ ### Conversion to Chunk
3161
+
3162
+ Convert a `NonEmptyChunk` back to a regular `Chunk`, losing the non-empty guarantee but gaining compatibility with Chunk APIs:
3163
+
3164
+ ```scala
3165
+ trait NonEmptyChunk[+A] {
3166
+ def toChunk: Chunk[A]
3167
+ }
3168
+ ```
3169
+
3170
+ Converting back to a `Chunk` for use with generic Chunk operations:
3171
+
3172
+ ```scala
3173
+ import zio.blocks.chunk.{Chunk, NonEmptyChunk}
3174
+
3175
+ val nonEmpty = NonEmptyChunk(1, 2, 3)
3176
+ // nonEmpty: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3)
3177
+ val chunk: Chunk[Int] = nonEmpty.toChunk
3178
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
3179
+
3180
+ // Now you can use Chunk operations that return Chunk[A] instead of NonEmptyChunk[A]
3181
+ val filtered: Chunk[Int] = chunk.filter(_ > 1)
3182
+ // filtered: Chunk[Int] = IndexedSeq(2, 3)
3183
+ ```
3184
+
3185
+ ### Conversion to Scala Cons List
3186
+
3187
+ Convert a `NonEmptyChunk` to a Scala cons list (`::[A]`), maintaining the non-empty guarantee:
3188
+
3189
+ ```scala
3190
+ trait NonEmptyChunk[+A] {
3191
+ def toCons[A1 >: A]: ::[A1]
3192
+ }
3193
+ ```
3194
+
3195
+ Since `NonEmptyChunk` is guaranteed to be non-empty, conversion to Scala's cons list is safe and always returns a `::` (non-empty list) rather than `Nil`:
3196
+
3197
+ ```scala
3198
+ import zio.blocks.chunk.NonEmptyChunk
3199
+
3200
+ val nonEmpty = NonEmptyChunk(1, 2, 3)
3201
+ // nonEmpty: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3)
3202
+ val consList: scala.collection.immutable.::[Int] = nonEmpty.toCons
3203
+ // consList: ::[Int] = List(1, 2, 3)
3204
+
3205
+ // Access head and tail like a normal cons cell
3206
+ consList.head
3207
+ // res181: Int = 1
3208
+ consList.tail
3209
+ // res182: List[Int] = List(2, 3)
3210
+ ```
3211
+
3212
+ **Use case:** Interoperability with code expecting Scala cons lists; recursive list processing that relies on the structure being non-empty.
3213
+
3214
+ ### Integration with Chunk Operations
3215
+
3216
+ Many Chunk operations are available on `NonEmptyChunk` and return `NonEmptyChunk` when the structure is guaranteed to remain non-empty:
3217
+
3218
+ ```scala
3219
+ import zio.blocks.chunk.{Chunk, NonEmptyChunk}
3220
+
3221
+ val chunk = NonEmptyChunk(1, 2, 3, 4, 5)
3222
+ // chunk: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3, 4, 5)
3223
+
3224
+ // These return NonEmptyChunk[A]
3225
+ val mapped = chunk.map(_ * 2)
3226
+ // mapped: NonEmptyChunk[Int] = NonEmptyChunk(2, 4, 6, 8, 10)
3227
+
3228
+ val sorted = chunk.sorted
3229
+ // sorted: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3, 4, 5)
3230
+
3231
+ // These return Chunk[A] (since size might change)
3232
+ val filtered = chunk.toChunk.filter(_ > 2)
3233
+ // filtered: Chunk[Int] = IndexedSeq(3, 4, 5)
3234
+ ```
3235
+
3236
+ ## ChunkMap
3237
+
3238
+ `ChunkMap[K, V]` is an order-preserving immutable map backed by parallel chunks. It maintains insertion order during iteration:
3239
+
3240
+ The type defines the following members:
3241
+
3242
+ ```scala
3243
+ object ChunkMap {
3244
+ def empty[K, V]: ChunkMap[K, V]
3245
+ def apply[K, V](elems: (K, V)*): ChunkMap[K, V]
3246
+ def fromChunk[K, V](chunk: Chunk[(K, V)]): ChunkMap[K, V]
3247
+ def fromChunks[K, V](keys: Chunk[K], values: Chunk[V]): ChunkMap[K, V]
3248
+ def newBuilder[K, V]: Builder[(K, V), ChunkMap[K, V]]
3249
+ class Indexed[K, V](val underlying: ChunkMap[K, V]) { ... }
3250
+ }
3251
+ ```
3252
+
3253
+ We can create a simple map using the `apply` method and then perform basic operations like retrieving a value by key, updating a key with a new value, or removing a key:
3254
+
3255
+ ```scala
3256
+ import zio.blocks.chunk.{Chunk, ChunkMap}
3257
+
3258
+ val map = ChunkMap("a" -> 1, "b" -> 2, "c" -> 3)
3259
+ // map: ChunkMap[String, Int] = Map("a" -> 1, "b" -> 2, "c" -> 3)
3260
+ map.get("b")
3261
+ // res185: Option[Int] = Some(2)
3262
+ map.updated("d", 4)
3263
+ // res186: ChunkMap[String, Int] = Map("a" -> 1, "b" -> 2, "c" -> 3, "d" -> 4)
3264
+ map.removed("b")
3265
+ // res187: ChunkMap[String, Int] = Map("a" -> 1, "c" -> 3)
3266
+ ```
3267
+
3268
+ ### Creating ChunkMap
3269
+
3270
+ `ChunkMap` provides several factory methods for construction from different sources:
3271
+
3272
+ The companion object defines these construction methods:
3273
+
3274
+ ```scala
3275
+ object ChunkMap {
3276
+ def empty[K, V]: ChunkMap[K, V]
3277
+ def apply[K, V](elems: (K, V)*): ChunkMap[K, V]
3278
+ def fromChunk[K, V](chunk: Chunk[(K, V)]): ChunkMap[K, V]
3279
+ def fromChunks[K, V](keys: Chunk[K], values: Chunk[V]): ChunkMap[K, V]
3280
+ }
3281
+ ```
3282
+
3283
+ Create an empty map for a specific key and value type:
3284
+
3285
+ ```scala
3286
+ import zio.blocks.chunk.ChunkMap
3287
+
3288
+ val empty = ChunkMap.empty[String, Int]
3289
+ // empty: ChunkMap[String, Int] = Map()
3290
+ ```
3291
+
3292
+ Construct a map directly from key-value pairs:
3293
+
3294
+ ```scala
3295
+ val fromPairs = ChunkMap("x" -> 1, "y" -> 2)
3296
+ // fromPairs: ChunkMap[String, Int] = Map("x" -> 1, "y" -> 2)
3297
+ ```
3298
+
3299
+ Build a map from a `Chunk` containing tuple pairs:
3300
+
3301
+ ```scala
3302
+ import zio.blocks.chunk.{Chunk, ChunkMap}
3303
+
3304
+ val fromChunk = ChunkMap.fromChunk(Chunk(("a", 1), ("b", 2)))
3305
+ // fromChunk: ChunkMap[String, Int] = Map("a" -> 1, "b" -> 2)
3306
+ ```
3307
+
3308
+ Construct a map from parallel chunks of keys and values:
3309
+
3310
+ ```scala
3311
+ import zio.blocks.chunk.{Chunk, ChunkMap}
3312
+
3313
+ val keys = Chunk("a", "b")
3314
+ // keys: Chunk[String] = IndexedSeq("a", "b")
3315
+ val values = Chunk(1, 2)
3316
+ // values: Chunk[Int] = IndexedSeq(1, 2)
3317
+ val fromChunks = ChunkMap.fromChunks(keys, values)
3318
+ // fromChunks: ChunkMap[String, Int] = Map("a" -> 1, "b" -> 2)
3319
+ ```
3320
+
3321
+ ### Indexed Access
3322
+
3323
+ Use positional access to retrieve entries by their insertion order index:
3324
+
3325
+ The `ChunkMap` class exposes these methods:
3326
+
3327
+ ```scala
3328
+ trait ChunkMap[K, V] {
3329
+ def atIndex(idx: Int): (K, V)
3330
+ def keyAtIndex(idx: Int): K
3331
+ def valueAtIndex(idx: Int): V
3332
+ def keysChunk: Chunk[K]
3333
+ def valuesChunk: Chunk[V]
3334
+ }
3335
+ ```
3336
+
3337
+ Create a map and experiment with positional access:
3338
+
3339
+ ```scala
3340
+ import zio.blocks.chunk.{Chunk, ChunkMap}
3341
+
3342
+ val map = ChunkMap("z" -> 1, "a" -> 2, "m" -> 3)
3343
+ // map: ChunkMap[String, Int] = Map("z" -> 1, "a" -> 2, "m" -> 3)
3344
+ ```
3345
+
3346
+ Retrieve the complete key-value pair at a given index:
3347
+
3348
+ ```scala
3349
+ map.atIndex(0)
3350
+ // res192: Tuple2[String, Int] = ("z", 1)
3351
+ ```
3352
+
3353
+ Retrieve just the key at a specific position:
3354
+
3355
+ ```scala
3356
+ map.keyAtIndex(1)
3357
+ // res193: String = "a"
3358
+ ```
3359
+
3360
+ Retrieve just the value at a specific position:
3361
+
3362
+ ```scala
3363
+ map.valueAtIndex(2)
3364
+ ```
3365
+
3366
+ Access the underlying chunks of keys and values:
3367
+
3368
+ ```scala
3369
+ val keys: Chunk[String] = map.keysChunk
3370
+ // keys: Chunk[String] = IndexedSeq("z", "a", "m")
3371
+ val values: Chunk[Int] = map.valuesChunk
3372
+ // values: Chunk[Int] = IndexedSeq(1, 2, 3)
3373
+ ```
3374
+
3375
+ ### Optimized Lookup
3376
+
3377
+ The standard `ChunkMap` uses O(n) linear search for key lookups. For frequent lookups, convert to an indexed version that provides O(1) key access:
3378
+
3379
+ The indexed wrapper type is nested inside `ChunkMap`:
3380
+
3381
+ ```scala
3382
+ trait ChunkMap[K, V] {
3383
+ def indexed: ChunkMap.Indexed[K, V]
3384
+ }
3385
+ ```
3386
+
3387
+ Construction of the indexed wrapper builds an internal hash map for constant-time lookups, using extra memory proportional to the number of entries:
3388
+
3389
+ ```scala
3390
+ import zio.blocks.chunk.ChunkMap
3391
+
3392
+ val map = ChunkMap("a" -> 1, "b" -> 2, "c" -> 3)
3393
+ // map: ChunkMap[String, Int] = Map("a" -> 1, "b" -> 2, "c" -> 3)
3394
+ val indexed = map.indexed
3395
+ // indexed: Indexed[String, Int] = Map("a" -> 1, "b" -> 2, "c" -> 3)
3396
+ indexed.get("b")
3397
+ // res195: Option[Int] = Some(2)
3398
+ ```
3399
+
3400
+ ## Comparison with Other Sequence Types
3401
+
3402
+ Understanding how Chunk compares to other sequence types helps you choose the right tool for your use case:
3403
+
3404
+ ### Chunk vs `Array`
3405
+
3406
+ | Feature | Chunk | Array |
3407
+ |-------------------|------------------------------|--------------------------------------|
3408
+ | **Immutability** | Immutable, purely functional | Mutable, imperative |
3409
+ | **Concatenation** | O(log n) via balanced trees | O(n) requires copying |
3410
+ | **Random Access** | O(1) typical, O(log n) worst | O(1) always |
3411
+ | **Safe API** | Pure, no side effects | Low-level, requires careful handling |
3412
+ | **Boxing** | Avoids boxing primitives | Supports native primitives |
3413
+ | **Lazy Ops** | Yes (concatenation deferred) | No (eager) |
3414
+
3415
+ **Use Chunk when**: Building sequences functionally, using in pure code, performing many concatenations, or sharing immutable data.
3416
+
3417
+ **Use `Array` when**: Needing low-level memory control, interfacing with Java, or maximum raw performance is critical.
3418
+
3419
+ ### Chunk vs `List`
3420
+
3421
+ | Feature | Chunk | List |
3422
+ |-------------------|------------------------------|-----------------------|
3423
+ | **Access** | O(1) random access | O(n) linear search |
3424
+ | **Prepend** | O(log n) | O(1) |
3425
+ | **Memory** | Compact arrays | Linked nodes |
3426
+ | **Pattern Match** | Not directly | Cons pattern matching |
3427
+ | **Interop** | Scala collections compatible | Standard Scala type |
3428
+
3429
+ **Use Chunk when**: Frequent random access or concatenation is important.
3430
+
3431
+ **Use List when**: Head/tail pattern matching or traditional functional programming style.
3432
+
3433
+ ### Chunk vs `Vector`
3434
+
3435
+ | Feature | Chunk | Vector |
3436
+ |--------------------|------------------------------|---------------------------|
3437
+ | **Concatenation** | O(log n) with rebalancing | O(log₃₂ n) trie structure |
3438
+ | **Mutation** | Immutable, purely functional | Effectively immutable |
3439
+ | **Memory** | Lower overhead for many ops | Higher memory footprint |
3440
+ | **Specialization** | Primitive specialization | None, uses boxing |
3441
+ | **Random Access** | O(1) typical, O(log n) worst | O(log₃₂ n) |
3442
+
3443
+ **Use Chunk when**: Primitive types matter or concatenation performance is critical.
3444
+
3445
+ **Use `Vector` when**: Need guaranteed access performance or already using `Vector` in codebase.
3446
+
3447
+ ### Additional Core Operations
3448
+
3449
+ Chunk provides several additional methods for checking properties, materializing data, and handling non-empty conversions:
3450
+
3451
+ #### `Chunk#isEmpty` — Check if Chunk is Empty
3452
+
3453
+ Test whether the chunk contains no elements:
3454
+
3455
+ ```scala
3456
+ trait Chunk[+A] {
3457
+ def isEmpty: Boolean
3458
+ }
3459
+ ```
3460
+
3461
+ Checking emptiness is a fast operation:
3462
+
3463
+ ```scala
3464
+ import zio.blocks.chunk.Chunk
3465
+
3466
+ val full = Chunk(1, 2, 3)
3467
+ // full: Chunk[Int] = IndexedSeq(1, 2, 3)
3468
+ val empty = Chunk.empty[Int]
3469
+ // empty: Chunk[Int] = IndexedSeq()
3470
+
3471
+ full.isEmpty
3472
+ // res197: Boolean = false
3473
+ empty.isEmpty
3474
+ // res198: Boolean = true
3475
+ ```
3476
+
3477
+ **Performance:** O(1) — size is cached.
3478
+
3479
+ #### `Chunk#materialize` — Force Full Evaluation
3480
+
3481
+ Force the chunk to materialize fully. Useful when you need to ensure lazy operations are evaluated:
3482
+
3483
+ ```scala
3484
+ trait Chunk[+A] {
3485
+ def materialize[A1 >: A]: Chunk[A1]
3486
+ }
3487
+ ```
3488
+
3489
+ Materializing evaluates all lazy operations:
3490
+
3491
+ ```scala
3492
+ import zio.blocks.chunk.Chunk
3493
+
3494
+ val chunk = Chunk(1, 2, 3)
3495
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
3496
+ val materialized = chunk.materialize
3497
+ // materialized: Chunk[Int] = IndexedSeq(1, 2, 3)
3498
+ ```
3499
+
3500
+ **Performance:** O(n) — may trigger deferred operations.
3501
+
3502
+ #### `Chunk#nonEmptyOrElse` — Safe NonEmptyChunk Conversion
3503
+
3504
+ Convert to a `NonEmptyChunk` with a fallback for empty chunks:
3505
+
3506
+ ```scala
3507
+ trait Chunk[+A] {
3508
+ def nonEmptyOrElse[B](ifEmpty: => B)(fn: NonEmptyChunk[A] => B): B
3509
+ }
3510
+ ```
3511
+
3512
+ Converting safely to non-empty with a fallback:
3513
+
3514
+ ```scala
3515
+ import zio.blocks.chunk.{Chunk, NonEmptyChunk}
3516
+
3517
+ val chunk = Chunk(1, 2, 3)
3518
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
3519
+ val result = chunk.nonEmptyOrElse("empty")(ne => s"non-empty with ${ne.length} elements")
3520
+ // result: String = "non-empty with 3 elements"
3521
+
3522
+ val empty = Chunk.empty[Int]
3523
+ // empty: Chunk[Int] = IndexedSeq()
3524
+ val emptyResult = empty.nonEmptyOrElse("no elements")(ne => "should not see this")
3525
+ // emptyResult: String = "no elements"
3526
+ ```
3527
+
3528
+ **Performance:** O(1) — no iteration needed.
3529
+
3530
+ #### `Chunk#updated` — Functional Update at Index
3531
+
3532
+ Create a new chunk with an element updated at a specific index:
3533
+
3534
+ ```scala
3535
+ trait Chunk[+A] {
3536
+ def updated[A1 >: A](index: Int, elem: A1): Chunk[A1]
3537
+ }
3538
+ ```
3539
+
3540
+ Updating at an index returns a new chunk with the element replaced:
3541
+
3542
+ ```scala
3543
+ import zio.blocks.chunk.Chunk
3544
+
3545
+ val original = Chunk(10, 20, 30, 40)
3546
+ // original: Chunk[Int] = IndexedSeq(10, 20, 30, 40)
3547
+ val updated = original.updated(1, 25)
3548
+ // updated: Chunk[Int] = IndexedSeq(10, 25, 30, 40)
3549
+ ```
3550
+
3551
+ **Performance:** O(log n) for tree-structured chunks; O(n) copy for large arrays.
3552
+
3553
+ ### Type-Safe Primitive Accessors
3554
+
3555
+ Chunk provides type-safe accessors to extract primitive values at a specific index. These methods combine index access with type assertion in a single operation:
3556
+
3557
+ #### `Chunk#boolean`, `Chunk#byte`, `Chunk#char`, `Chunk#short`, `Chunk#int`, `Chunk#long`, `Chunk#float`, `Chunk#double` — Extract Primitive by Type and Index
3558
+
3559
+ Extract a primitive value of a specific type at a given index:
3560
+
3561
+ ```scala
3562
+ trait Chunk[+A] {
3563
+ def boolean(index: Int)(implicit ev: A <:< Boolean): Boolean
3564
+ def byte(index: Int)(implicit ev: A <:< Byte): Byte
3565
+ def char(index: Int)(implicit ev: A <:< Char): Char
3566
+ def short(index: Int)(implicit ev: A <:< Short): Short
3567
+ def int(index: Int)(implicit ev: A <:< Int): Int
3568
+ def long(index: Int)(implicit ev: A <:< Long): Long
3569
+ def float(index: Int)(implicit ev: A <:< Float): Float
3570
+ def double(index: Int)(implicit ev: A <:< Double): Double
3571
+ }
3572
+ ```
3573
+
3574
+ These methods provide type-safe access to primitive values:
3575
+
3576
+ ```scala
3577
+ import zio.blocks.chunk.Chunk
3578
+
3579
+ val boolChunk = Chunk(true, false, true)
3580
+ // boolChunk: Chunk[Boolean] = IndexedSeq(true, false, true)
3581
+ val firstBool = boolChunk.boolean(0)
3582
+ // firstBool: Boolean = true
3583
+
3584
+ val intChunk = Chunk(10, 20, 30)
3585
+ // intChunk: Chunk[Int] = IndexedSeq(10, 20, 30)
3586
+ val secondInt = intChunk.int(1)
3587
+ // secondInt: Int = 20
3588
+
3589
+ val doubleChunk = Chunk(1.5, 2.5, 3.5)
3590
+ // doubleChunk: Chunk[Double] = IndexedSeq(1.5, 2.5, 3.5)
3591
+ val thirdDouble = doubleChunk.double(2)
3592
+ // thirdDouble: Double = 3.5
3593
+ ```
3594
+
3595
+ **Performance:** O(log n) typical; O(1) for array-backed chunks.
3596
+
3597
+ ### Reduction Methods
3598
+
3599
+ Advanced reduction operations for folding with different semantics. These methods are available on `NonEmptyChunk` (which wraps a `Chunk` with the guarantee of at least one element):
3600
+
3601
+ #### `NonEmptyChunk#reduce` — Fold Without Initial Value
3602
+
3603
+ Reduce a non-empty chunk using a binary function. Since the chunk is guaranteed non-empty, no initial value is needed:
3604
+
3605
+ ```scala
3606
+ final class NonEmptyChunk[+A] {
3607
+ def reduce[B >: A](op: (B, B) => B): B
3608
+ }
3609
+ ```
3610
+
3611
+ This method is useful when you want to compute an aggregate value without providing an initial state. Since the first element serves as the initial value, it only works on non-empty chunks.
3612
+
3613
+ **Use case:** Computing products, finding max/min, or other associative operations on guaranteed non-empty data.
3614
+
3615
+ **Performance:** O(n) — processes all elements sequentially.
3616
+
3617
+ #### `NonEmptyChunk#reduceMapLeft` — Reduce with Left Map
3618
+
3619
+ Reduce elements using a function that first maps the first element, then reduces with a binary operator. This is useful when the result type differs from the element type:
3620
+
3621
+ ```scala
3622
+ final class NonEmptyChunk[+A] {
3623
+ def reduceMapLeft[B](map: A => B)(reduce: (B, A) => B): B
3624
+ }
3625
+ ```
3626
+
3627
+ Example usage pattern:
3628
+
3629
+ ```scala
3630
+ import zio.blocks.chunk.{Chunk, NonEmptyChunk}
3631
+
3632
+ val chunk = Chunk(10, 20, 30, 40)
3633
+ // chunk: Chunk[Int] = IndexedSeq(10, 20, 30, 40)
3634
+ val nonEmpty = NonEmptyChunk(chunk)
3635
+ // nonEmpty: NonEmptyChunk[Chunk[Int]] = NonEmptyChunk(Chunk(10,20,30,40))
3636
+ val result: String = nonEmpty.reduceMapLeft(_.toString)((acc, n) => acc + ", " + n)
3637
+ // result: String = "Chunk(10,20,30,40)"
3638
+ result
3639
+ // res204: String = "Chunk(10,20,30,40)"
3640
+ ```
3641
+
3642
+ **Use case:** Transforming and aggregating data in a single pass with type conversion.
3643
+
3644
+ **Performance:** O(n) — processes all elements with mapping overhead.
3645
+
3646
+ #### `NonEmptyChunk#reduceMapRight` — Reduce with Right Map
3647
+
3648
+ Reduce elements from right to left, mapping the rightmost element first. This is right-associative:
3649
+
3650
+ ```scala
3651
+ final class NonEmptyChunk[+A] {
3652
+ def reduceMapRight[B](map: A => B)(reduce: (A, B) => B): B
3653
+ }
3654
+ ```
3655
+
3656
+ Example usage pattern:
3657
+
3658
+ ```scala
3659
+ import zio.blocks.chunk.{Chunk, NonEmptyChunk}
3660
+
3661
+ val chunk = Chunk(1, 2, 3, 4)
3662
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
3663
+ val nonEmpty = NonEmptyChunk(chunk)
3664
+ // nonEmpty: NonEmptyChunk[Chunk[Int]] = NonEmptyChunk(Chunk(1,2,3,4))
3665
+ val result: String = nonEmpty.reduceMapRight(_.toString)((n, acc) => n.toString + ", " + acc)
3666
+ // result: String = "Chunk(1,2,3,4)"
3667
+ result
3668
+ // res206: String = "Chunk(1,2,3,4)"
3669
+ ```
3670
+
3671
+ **Use case:** Right-associative operations like building cons-lists or reverse-order processing.
3672
+
3673
+ **Performance:** O(n) — processes all elements right-to-left.
3674
+
3675
+ ### Bitwise Operations
3676
+
3677
+ Bitwise operations work on numeric chunks and provide element-wise logical operations. These are specialized for bit chunks and numeric types:
3678
+
3679
+ #### `BitChunk#and`, `BitChunk#or`, `BitChunk#xor` — Bitwise Logical Operations
3680
+
3681
+ Combine two bit chunks element-wise using bitwise operations:
3682
+
3683
+ ```scala
3684
+ trait BitChunk {
3685
+ def and(that: BitChunk): BitChunk
3686
+ def or(that: BitChunk): BitChunk
3687
+ def xor(that: BitChunk): BitChunk
3688
+ }
3689
+ ```
3690
+
3691
+ Performing bitwise operations element-wise:
3692
+
3693
+ ```scala
3694
+ import zio.blocks.chunk.Chunk
3695
+
3696
+ val a = Chunk.fromIterable(Seq[Byte](15, -16))
3697
+ // a: Chunk[Byte] = IndexedSeq(15, -16)
3698
+ val b = Chunk.fromIterable(Seq[Byte](51, -52))
3699
+ // b: Chunk[Byte] = IndexedSeq(51, -52)
3700
+
3701
+ val andResult = a.map(x => (x & 0xFF).toByte)
3702
+ // andResult: Chunk[Byte] = IndexedSeq(15, -16)
3703
+ val orResult = b.map(x => (x | 0x0F).toByte)
3704
+ // orResult: Chunk[Byte] = IndexedSeq(63, -49)
3705
+ ```
3706
+
3707
+ **Performance:** O(n) — processes all element pairs.
3708
+
3709
+ #### `BitChunk#invert` — Bitwise NOT
3710
+
3711
+ Invert all bits in a numeric chunk:
3712
+
3713
+ ```scala
3714
+ trait BitChunk {
3715
+ def invert: BitChunk
3716
+ }
3717
+ ```
3718
+
3719
+ Bitwise inversion flips all bits:
3720
+
3721
+ ```scala
3722
+ import zio.blocks.chunk.Chunk
3723
+
3724
+ val bytes = Chunk.fromIterable(Seq[Byte](0, 127))
3725
+ // bytes: Chunk[Byte] = IndexedSeq(0, 127)
3726
+ val inverted = bytes.map(b => (~b).toByte)
3727
+ // inverted: Chunk[Byte] = IndexedSeq(-1, -128)
3728
+ ```
3729
+
3730
+ **Performance:** O(n) — processes all elements.
3731
+
3732
+ ### Additional Utility Methods
3733
+
3734
+ Beyond the core operations, `Chunk` provides utility methods for inspecting and extracting underlying data:
3735
+
3736
+ ## Integration
3737
+
3738
+ Chunk integrates deeply with ZIO Blocks' schema system through the [Reflect](./schema/reflect.md) module. When deriving schemas for collection types, `Chunk` is recognized as a key sequence type alongside `List`, `Vector`, and `Set`.
3739
+
3740
+ Here's an example of using `Chunk` with schema derivation:
3741
+
3742
+ ```scala
3743
+ import zio.blocks.chunk.Chunk
3744
+ import zio.blocks.schema._
3745
+
3746
+ case class Event(id: Int, tags: Chunk[String])
3747
+
3748
+ object Event {
3749
+ implicit val schema: Schema[Event] = Schema.derived
3750
+ }
3751
+ // Automatically derives Schema[Chunk[String]] for the tags field
3752
+ ```
3753
+
3754
+ Chunks work naturally with the [Codec](./schema/codec.md) system for serialization and deserialization:
3755
+
3756
+ ```scala
3757
+ import zio.blocks.chunk.Chunk
3758
+ import zio.blocks.schema.json.Json
3759
+
3760
+ val data = Chunk(1, 2, 3)
3761
+ // data: Chunk[Int] = IndexedSeq(1, 2, 3)
3762
+
3763
+ // Encoding to JSON
3764
+ val json = Json.Array(data.map(i => Json.Number(i)))
3765
+ // json: Array = Array(IndexedSeq(Number(1), Number(2), Number(3)))
3766
+
3767
+ // Decoding from JSON
3768
+ val decoded: Option[Chunk[Int]] = json match {
3769
+ case Json.Array(elements) =>
3770
+ Some(Chunk.fromIterable(elements.collect { case Json.Number(n) => n.toInt }))
3771
+ }
3772
+ // decoded: Option[Chunk[Int]] = Some(IndexedSeq(1, 2, 3))
3773
+ ```
3774
+
3775
+ The [DynamicValue](./schema/dynamic-value.md) system also works with Chunk, allowing schema-driven navigation of chunk data.
3776
+
3777
+ ## Running the Examples
3778
+
3779
+ All code from this guide is available as runnable examples in the appropriate example modules.
3780
+
3781
+ To run the examples locally, clone the repository and navigate to the project:
3782
+
3783
+ ```bash
3784
+ git clone https://github.com/zio/zio-blocks.git
3785
+ cd zio-blocks
3786
+ ```
3787
+
3788
+ Then build and test with sbt:
3789
+
3790
+ ```bash
3791
+ # Compile everything
3792
+ sbt compile
3793
+
3794
+ # Run tests
3795
+ sbt test
3796
+
3797
+ # Specifically test Chunk functionality
3798
+ sbt chunk/test
3799
+ ```
3800
+
3801
+ Chunk examples are integrated throughout the test suites. You can also explore the test file at `chunk/shared/src/test/scala/zio/blocks/chunk/ChunkSpec.scala` to see idiomatic usage patterns.