@zio.dev/zio-blocks 0.0.28 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guides/query-dsl-extending.md +1 -1
- package/guides/query-dsl-fluent-builder.md +1 -1
- package/guides/query-dsl-reified-optics.md +1 -1
- package/guides/query-dsl-sql.md +1 -1
- package/guides/zio-schema-migration.md +6 -6
- package/index.md +62 -16
- package/package.json +1 -1
- package/path-interpolator.md +70 -9
- package/reference/allows.md +96 -0
- package/reference/codec.md +8 -8
- package/reference/combinators.md +345 -0
- package/reference/context.md +639 -67
- package/reference/docs.md +1 -1
- package/reference/http-model.md +1716 -0
- package/reference/json-differ.md +320 -0
- package/reference/json-patch.md +1 -1
- package/reference/media-type.md +2 -2
- package/reference/resource-management-di/index.md +49 -0
- package/reference/resource-management-di/resource.md +1125 -0
- package/{scope.md → reference/resource-management-di/scope.md} +1 -1
- package/reference/resource-management-di/wire.md +832 -0
- package/reference/schema-evolution/as.md +4 -4
- package/reference/schema-evolution/into.md +2 -2
- package/reference/schema-expr.md +2 -2
- package/reference/type-class-derivation.md +31 -31
- package/ringbuffer.md +249 -0
- package/sidebars.js +14 -2
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: combinators
|
|
3
|
+
title: "Combinators"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
The `combinators` module provides compile-time typeclasses for composing and decomposing values in type-safe ways. Each module focuses on a specific domain: tuples, Either types, and union types.
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
The combinators module consists of three core modules:
|
|
11
|
+
|
|
12
|
+
- **Tuples** - Tuple composition with automatic flattening and separation
|
|
13
|
+
- **Eithers** - Either canonicalization to left-nested form
|
|
14
|
+
- **Unions** - Union type operations (Scala 3 only)
|
|
15
|
+
|
|
16
|
+
Each module provides:
|
|
17
|
+
- A unified typeclass (e.g., `Tuples.Tuples[L, R]`) that provides both `combine` and `separate` operations
|
|
18
|
+
- A convenience method `combine` (the `separate` operation is available on the typeclass instance)
|
|
19
|
+
|
|
20
|
+
All typeclasses are derived automatically via compile-time resolution and provide zero-cost abstractions.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Add the following to your `build.sbt`:
|
|
25
|
+
|
|
26
|
+
```scala
|
|
27
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-combinators" % "<version>"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
For cross-platform projects (Scala.js):
|
|
31
|
+
|
|
32
|
+
```scala
|
|
33
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-combinators" % "<version>"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Supported platforms:
|
|
37
|
+
- **Tuples, Eithers**: JVM, Scala.js (Scala 2.13 and 3.x)
|
|
38
|
+
- **Unions**: JVM, Scala.js (Scala 3 only)
|
|
39
|
+
|
|
40
|
+
## Tuples
|
|
41
|
+
|
|
42
|
+
The `Tuples` module combines values into flat tuples and separates them back.
|
|
43
|
+
|
|
44
|
+
### combine
|
|
45
|
+
|
|
46
|
+
`Tuples.Tuples[L, R]` combines two values into a flattened tuple.
|
|
47
|
+
|
|
48
|
+
```scala
|
|
49
|
+
import zio.blocks.combinators.Tuples
|
|
50
|
+
|
|
51
|
+
// Basic combination
|
|
52
|
+
val result1: (Int, String) = Tuples.combine(1, "hello")
|
|
53
|
+
|
|
54
|
+
// Tuple flattening
|
|
55
|
+
val result2: (Int, String, Boolean) = Tuples.combine((1, "hello"), true)
|
|
56
|
+
|
|
57
|
+
// Deep flattening (Scala 3)
|
|
58
|
+
val result3: (Int, String, Boolean, Double) = Tuples.combine((1, "hello"), (true, 3.14))
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### Identity Handling
|
|
62
|
+
|
|
63
|
+
Unit and EmptyTuple values are automatically eliminated:
|
|
64
|
+
|
|
65
|
+
```scala
|
|
66
|
+
import zio.blocks.combinators.Tuples
|
|
67
|
+
|
|
68
|
+
// Unit on left - returns right value
|
|
69
|
+
val result1: Int = Tuples.combine((), 42)
|
|
70
|
+
|
|
71
|
+
// Unit on right - returns left value
|
|
72
|
+
val result2: String = Tuples.combine("hello", ())
|
|
73
|
+
|
|
74
|
+
// EmptyTuple identity (Scala 3)
|
|
75
|
+
val result3: String = Tuples.combine(EmptyTuple, "world")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### Tuple Flattening
|
|
79
|
+
|
|
80
|
+
Nested tuples are automatically flattened:
|
|
81
|
+
|
|
82
|
+
```scala
|
|
83
|
+
import zio.blocks.combinators.Tuples
|
|
84
|
+
|
|
85
|
+
// Tuple + value flattens to larger tuple
|
|
86
|
+
val result1: (Int, String, Boolean) = Tuples.combine((1, "a"), true)
|
|
87
|
+
|
|
88
|
+
// Tuple + tuple concatenates (Scala 3 - recursive flattening)
|
|
89
|
+
val result2: (Int, String, Boolean, Double) = Tuples.combine((1, "a"), (true, 3.14))
|
|
90
|
+
|
|
91
|
+
// Scala 2 - flattens left tuple only
|
|
92
|
+
val result3: (Int, String, (Boolean, Double)) = Tuples.combine((1, "a"), (true, 3.14))
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### separate
|
|
96
|
+
|
|
97
|
+
`separate` is accessed via the unified typeclass instance and splits a tuple into its init (all but last) and last element.
|
|
98
|
+
|
|
99
|
+
```scala
|
|
100
|
+
import zio.blocks.combinators.Tuples
|
|
101
|
+
|
|
102
|
+
// 2-tuple separation
|
|
103
|
+
val t2 = summon[Tuples.Tuples[Int, String]] // Scala 3
|
|
104
|
+
// or: implicitly[Tuples.Tuples[Int, String]] // Scala 2
|
|
105
|
+
val (left1, right1): (Int, String) = t2.separate((1, "hello"))
|
|
106
|
+
// left1 = 1, right1 = "hello"
|
|
107
|
+
|
|
108
|
+
// 3-tuple separation
|
|
109
|
+
val t3 = summon[Tuples.Tuples[(Int, String), Boolean]]
|
|
110
|
+
val (left2, right2): ((Int, String), Boolean) = t3.separate((1, "hello", true))
|
|
111
|
+
// left2 = (1, "hello"), right2 = true
|
|
112
|
+
|
|
113
|
+
// 4-tuple separation
|
|
114
|
+
val t4 = summon[Tuples.Tuples[(Int, String, Boolean), Double]]
|
|
115
|
+
val (left3, right3): ((Int, String, Boolean), Double) = t4.separate((1, "hello", true, 3.14))
|
|
116
|
+
// left3 = (1, "hello", true), right3 = 3.14
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
### Type-Level Operations
|
|
121
|
+
|
|
122
|
+
The output type is computed at compile time via the `Out` type member:
|
|
123
|
+
|
|
124
|
+
```scala
|
|
125
|
+
import zio.blocks.combinators.Tuples
|
|
126
|
+
|
|
127
|
+
// Access the combiner with explicit output type
|
|
128
|
+
val combiner: Tuples.Tuples.WithOut[Int, String, (Int, String)] =
|
|
129
|
+
summon[Tuples.Tuples[Int, String]]
|
|
130
|
+
|
|
131
|
+
// Access with explicit types
|
|
132
|
+
val instance: Tuples.Tuples.WithOut[Int, String, (Int, String)] =
|
|
133
|
+
summon[Tuples.Tuples[Int, String]]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Scala 2 vs Scala 3 Differences
|
|
137
|
+
|
|
138
|
+
| Feature | Scala 2.13 | Scala 3.x |
|
|
139
|
+
|---------|------------|-----------|
|
|
140
|
+
| Maximum tuple arity | 22 | Unlimited |
|
|
141
|
+
| Tuple flattening | Left tuple only | Recursive both sides |
|
|
142
|
+
| EmptyTuple identity | Not available | Supported |
|
|
143
|
+
|
|
144
|
+
## Eithers
|
|
145
|
+
|
|
146
|
+
The `Eithers` module canonicalizes Either types to left-nested form and separates them.
|
|
147
|
+
|
|
148
|
+
### combine
|
|
149
|
+
|
|
150
|
+
`Eithers.Eithers[L, R]` transforms an `Either[L, R]` into its left-nested canonical form.
|
|
151
|
+
|
|
152
|
+
```scala
|
|
153
|
+
import zio.blocks.combinators.Eithers
|
|
154
|
+
|
|
155
|
+
// Atomic Either - unchanged
|
|
156
|
+
val result1: Either[Int, String] = Eithers.combine(Left(42): Either[Int, String])
|
|
157
|
+
|
|
158
|
+
// Right-nested Either - reassociates to left-nested
|
|
159
|
+
val input: Either[Int, Either[String, Boolean]] = Right(Right(true))
|
|
160
|
+
val result2: Either[Either[Int, String], Boolean] = Eithers.combine(input)
|
|
161
|
+
// Right(Right(true)) becomes Right(true)
|
|
162
|
+
|
|
163
|
+
// Left(42) becomes Left(Left(42))
|
|
164
|
+
val input2: Either[Int, Either[String, Boolean]] = Left(42)
|
|
165
|
+
val result3: Either[Either[Int, String], Boolean] = Eithers.combine(input2)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Canonical Form
|
|
169
|
+
|
|
170
|
+
The canonical form is always left-nested:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
Right-nested input: Left-nested output:
|
|
174
|
+
Either[A, Either[B, C]] => Either[Either[A, B], C]
|
|
175
|
+
Either[A, Either[B, Either[C, D]]] => Either[Either[Either[A, B], C], D]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
This transformation preserves values while reassociating the structure:
|
|
179
|
+
- `Left(a)` → `Left(Left(a))`
|
|
180
|
+
- `Right(Left(b))` → `Left(Right(b))`
|
|
181
|
+
- `Right(Right(c))` → `Right(c)`
|
|
182
|
+
|
|
183
|
+
### separate
|
|
184
|
+
|
|
185
|
+
`separate` is accessed via the unified typeclass instance and peels the rightmost alternative from a canonical Either:
|
|
186
|
+
|
|
187
|
+
```scala
|
|
188
|
+
import zio.blocks.combinators.Eithers
|
|
189
|
+
|
|
190
|
+
val e = summon[Eithers.Eithers[Int, String]]
|
|
191
|
+
val input: Either[Int, String] = Left(42)
|
|
192
|
+
val result: Either[Int, String] = e.separate(e.combine(input))
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Use Cases
|
|
196
|
+
|
|
197
|
+
Eithers canonicalization is useful for:
|
|
198
|
+
- **Schema sum type encoding** - Uniform representation of sealed traits
|
|
199
|
+
- **Error handling composition** - Combining error types systematically
|
|
200
|
+
- **Cross-version compatibility** - Works identically on Scala 2 and 3
|
|
201
|
+
|
|
202
|
+
## Unions (Scala 3 Only)
|
|
203
|
+
|
|
204
|
+
The `Unions` module converts between Either types and Scala 3 union types.
|
|
205
|
+
|
|
206
|
+
### combine
|
|
207
|
+
|
|
208
|
+
`Unions.Unions[L, R]` converts an `Either[L, R]` to a union type `L | R`:
|
|
209
|
+
|
|
210
|
+
```scala
|
|
211
|
+
import zio.blocks.combinators.Unions
|
|
212
|
+
|
|
213
|
+
val either: Either[Int, String] = Left(42)
|
|
214
|
+
val union: Int | String = Unions.combine(either)
|
|
215
|
+
// Result: 42 (typed as Int | String)
|
|
216
|
+
|
|
217
|
+
val either2: Either[Int, String] = Right("hello")
|
|
218
|
+
val union2: Int | String = Unions.combine(either2)
|
|
219
|
+
// Result: "hello" (typed as Int | String)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### separate
|
|
223
|
+
|
|
224
|
+
`separate` is accessed via the unified typeclass instance and discriminates a union type back to Either:
|
|
225
|
+
|
|
226
|
+
```scala
|
|
227
|
+
import zio.blocks.combinators.Unions
|
|
228
|
+
|
|
229
|
+
val u = summon[Unions.Unions.WithOut[Int, String, Int | String]]
|
|
230
|
+
val result: Either[Int, String] = u.separate(42: Int | String)
|
|
231
|
+
// Result: Left(42)
|
|
232
|
+
|
|
233
|
+
val result2: Either[Int, String] = u.separate("hello": Int | String)
|
|
234
|
+
// Result: Right("hello")
|
|
235
|
+
```
|
|
236
|
+
### Same-Type Rejection
|
|
237
|
+
|
|
238
|
+
Union types collapse same types (`A | A` = `A`), making them ambiguous. The separator rejects overlapping types at compile time:
|
|
239
|
+
|
|
240
|
+
```scala
|
|
241
|
+
import zio.blocks.combinators.Unions
|
|
242
|
+
|
|
243
|
+
// Compile error: Union types must contain unique types
|
|
244
|
+
// val u = summon[Unions.Unions.WithOut[Int, Int, Int | Int]]
|
|
245
|
+
|
|
246
|
+
// Use Either for same-type alternation instead:
|
|
247
|
+
import zio.blocks.combinators.Eithers
|
|
248
|
+
val either: Either[Int, Int] = Left(1) // Distinguishable via Left/Right
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Type Erasure Caveat
|
|
252
|
+
|
|
253
|
+
Union discrimination relies on runtime type tests, which are fragile for erased types:
|
|
254
|
+
|
|
255
|
+
```scala
|
|
256
|
+
// Problematic: List[Int] and List[String] erase to List
|
|
257
|
+
val value: List[Int] | List[String] = List(1, 2, 3)
|
|
258
|
+
// Runtime cannot distinguish List[Int] from List[String]
|
|
259
|
+
|
|
260
|
+
// Safe: Use distinct concrete types
|
|
261
|
+
val value: Int | String = 42 // Works reliably
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Generic Usage Patterns
|
|
265
|
+
|
|
266
|
+
### With Implicit Parameters (Scala 2)
|
|
267
|
+
|
|
268
|
+
```scala
|
|
269
|
+
import zio.blocks.combinators.Tuples
|
|
270
|
+
|
|
271
|
+
def combineAll[A, B, C](a: A, b: B, c: C)(
|
|
272
|
+
implicit ab: Tuples.Tuples[A, B],
|
|
273
|
+
abc: Tuples.Tuples[ab.Out, C]
|
|
274
|
+
): abc.Out = {
|
|
275
|
+
val step1 = ab.combine(a, b)
|
|
276
|
+
abc.combine(step1, c)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
val result = combineAll(1, "hello", true)
|
|
280
|
+
// result: (Int, String, Boolean)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### With Context Parameters (Scala 3)
|
|
284
|
+
|
|
285
|
+
```scala
|
|
286
|
+
import zio.blocks.combinators.Tuples
|
|
287
|
+
|
|
288
|
+
def combineAll[A, B, C](a: A, b: B, c: C)(using
|
|
289
|
+
ab: Tuples.Tuples[A, B],
|
|
290
|
+
abc: Tuples.Tuples[ab.Out, C]
|
|
291
|
+
): abc.Out =
|
|
292
|
+
val step1 = ab.combine(a, b)
|
|
293
|
+
abc.combine(step1, c)
|
|
294
|
+
|
|
295
|
+
val result = combineAll(1, "hello", true)
|
|
296
|
+
// result: (Int, String, Boolean)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Path-Dependent Types
|
|
300
|
+
|
|
301
|
+
The `Out`, `Left`, and `Right` type members are path-dependent:
|
|
302
|
+
|
|
303
|
+
```scala
|
|
304
|
+
import zio.blocks.combinators.Tuples
|
|
305
|
+
|
|
306
|
+
def process[L, R](l: L, r: R)(using t: Tuples.Tuples[L, R]): (L, R) =
|
|
307
|
+
t.separate(t.combine(l, r))
|
|
308
|
+
|
|
309
|
+
val result: (Int, String) = process(1, "hello")
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Type Aliases for Clarity
|
|
313
|
+
|
|
314
|
+
```scala
|
|
315
|
+
import zio.blocks.combinators.Tuples
|
|
316
|
+
|
|
317
|
+
// Typeclass with known output type
|
|
318
|
+
type IntStringTuples = Tuples.Tuples.WithOut[Int, String, (Int, String)]
|
|
319
|
+
|
|
320
|
+
// Typeclass with known left/right types
|
|
321
|
+
type TripleTuples = Tuples.Tuples.WithOut[(Int, String), Boolean, (Int, String, Boolean)]
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Performance Characteristics
|
|
325
|
+
|
|
326
|
+
| Module | Time Complexity | Notes |
|
|
327
|
+
|--------|-----------------|-------|
|
|
328
|
+
| Tuples.combine | O(1) to O(n) | O(1) for small tuples; O(n) for flattening nested tuples |
|
|
329
|
+
| Tuples.separate | O(n) | Splits tuple at size-1 position |
|
|
330
|
+
| Eithers.combine | O(d) | d = nesting depth of right-nested Either |
|
|
331
|
+
| Eithers.separate | O(d) | Same as combine (delegates to combiner) |
|
|
332
|
+
| Unions.combine | O(1) | Direct Either fold |
|
|
333
|
+
| Unions.separate | O(1) | Single type test |
|
|
334
|
+
|
|
335
|
+
All operations are pure and allocation-minimal.
|
|
336
|
+
|
|
337
|
+
## Cross-Version Summary
|
|
338
|
+
|
|
339
|
+
| Feature | Scala 2.13 | Scala 3.x |
|
|
340
|
+
|---------|------------|-----------|
|
|
341
|
+
| Tuples.Tuples | Yes (max 22) | Yes (unlimited) |
|
|
342
|
+
| Eithers.Eithers | Yes | Yes |
|
|
343
|
+
| Unions.Unions | No | Yes |
|
|
344
|
+
| Recursive tuple flattening | No | Yes |
|
|
345
|
+
| EmptyTuple handling | No | Yes |
|