@zio.dev/zio-blocks 0.0.1 → 0.0.21
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/index.md +143 -15
- package/package.json +5 -2
- package/reference/binding.md +8 -14
- package/reference/chunk.md +36 -36
- package/reference/docs.md +2 -2
- package/reference/dynamic-value.md +34 -34
- package/reference/formats.md +16 -16
- package/reference/json-schema.md +20 -20
- package/reference/json.md +47 -47
- package/reference/optics.md +51 -37
- package/reference/reflect.md +3 -3
- package/reference/registers.md +7 -7
- package/reference/schema.md +18 -18
- package/reference/syntax.md +16 -16
- package/scope.md +481 -161
- package/reference/modifier.md +0 -276
- package/reference/reflect-transform.md +0 -387
- package/reference/type-class-derivation-internals.md +0 -632
|
@@ -1,632 +0,0 @@
|
|
|
1
|
-
# Type-Class Derivation Internals: A Step-by-Step Deep Dive
|
|
2
|
-
|
|
3
|
-
This document explains the internal mechanics of how ZIO Blocks derives type-class instances (like `Show`, `Eq`, `Encoder`, etc.) from a schema. We use the `Show` type-class derivation for a simple `Person` record as our running example.
|
|
4
|
-
|
|
5
|
-
> **Related Documentation**: For a high-level overview of the `Deriver` API and how to implement custom derivers, see [Type Class Derivation](type-class-derivation.md).
|
|
6
|
-
|
|
7
|
-
> **Example Code**: The traces in this document are generated from `DeriveShowApproachOne.scala` in the `zio-blocks-examples` module. Run it with `sbt "examples/runMain typeclassderivation.DeriveShowApproachOne"` to see the full execution trace.
|
|
8
|
-
|
|
9
|
-
## Table of Contents
|
|
10
|
-
|
|
11
|
-
1. [Overview](#overview)
|
|
12
|
-
2. [The Key Components](#the-key-components)
|
|
13
|
-
3. [The Complete Flow](#the-complete-flow)
|
|
14
|
-
4. [Step-by-Step Walkthrough](#step-by-step-walkthrough)
|
|
15
|
-
5. [Why Lazy Evaluation?](#why-lazy-evaluation)
|
|
16
|
-
6. [Execution Trace Analysis](#execution-trace-analysis)
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## Overview
|
|
21
|
-
|
|
22
|
-
When you write:
|
|
23
|
-
|
|
24
|
-
```scala
|
|
25
|
-
case class Person(name: String, age: Int)
|
|
26
|
-
|
|
27
|
-
object Person {
|
|
28
|
-
implicit val schema: Schema[Person] = Schema.derived[Person]
|
|
29
|
-
implicit val show: Show[Person] = schema.derive(DeriveShow)
|
|
30
|
-
}
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
The framework performs a sophisticated transformation that:
|
|
34
|
-
|
|
35
|
-
1. **Traverses** the schema tree (which describes the structure of `Person`)
|
|
36
|
-
2. **Transforms** each node, wrapping the runtime `Binding` with a `Lazy[TC[A]]` instance
|
|
37
|
-
3. **Returns** the derived type-class instance at the root
|
|
38
|
-
|
|
39
|
-
The key insight is that derivation is a **tree transformation** where each node gets augmented with its corresponding type-class instance.
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## The Key Components
|
|
44
|
-
|
|
45
|
-
### 1. Schema and Reflect
|
|
46
|
-
|
|
47
|
-
A `Schema[A]` is a wrapper around `Reflect[Binding, A]`, which is a tree structure describing your data type:
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
Schema[Person]
|
|
51
|
-
└── Reflect.Record[Binding, Person]
|
|
52
|
-
├── Term("name", Reflect.Primitive[Binding, String])
|
|
53
|
-
└── Term("age", Reflect.Primitive[Binding, Int])
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Each node carries:
|
|
57
|
-
- **Type information** (TypeId, PrimitiveType for primitives)
|
|
58
|
-
- **Binding** - runtime machinery for constructing/deconstructing values
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
### 4. ReflectTransformer[F, G]
|
|
62
|
-
|
|
63
|
-
The transformation is performed by a `ReflectTransformer` that visits each node:
|
|
64
|
-
|
|
65
|
-
```scala
|
|
66
|
-
trait ReflectTransformer[F[_, _], G[_, _]] {
|
|
67
|
-
def transformPrimitive[A](...): Lazy[Reflect.Primitive[G, A]]
|
|
68
|
-
def transformRecord[A](...): Lazy[Reflect.Record[G, A]]
|
|
69
|
-
// ... etc
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## The Complete Flow
|
|
74
|
-
|
|
75
|
-
```
|
|
76
|
-
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
77
|
-
│ Type-Class Derivation Flow │
|
|
78
|
-
└─────────────────────────────────────────────────────────────────────────────┘
|
|
79
|
-
|
|
80
|
-
INPUT:
|
|
81
|
-
Schema[Person] containing Reflect[Binding, Person]
|
|
82
|
-
|
|
83
|
-
PHASE 1: Tree Transformation (Bottom-Up)
|
|
84
|
-
┌──────────────────────────────────────────────────────────────────────────┐
|
|
85
|
-
│ For each node in the schema tree (leaves first, then parents): │
|
|
86
|
-
│ │
|
|
87
|
-
│ 1. Transform child nodes first (if any) │
|
|
88
|
-
│ 2. Call the appropriate Deriver method to get Lazy[TC[A]] │
|
|
89
|
-
│ 3. Wrap the Binding with the Lazy[TC[A]] into a BindingInstance │
|
|
90
|
-
│ 4. Return the transformed Reflect node │
|
|
91
|
-
└──────────────────────────────────────────────────────────────────────────┘
|
|
92
|
-
|
|
93
|
-
Transformation sequence for Person:
|
|
94
|
-
|
|
95
|
-
Step 1: Transform Primitive "name" (String)
|
|
96
|
-
→ deriver.derivePrimitive(String) → Lazy[Show[String]]
|
|
97
|
-
→ BindingInstance(Binding.Primitive, Lazy[Show[String]])
|
|
98
|
-
|
|
99
|
-
Step 2: Transform Primitive "age" (Int)
|
|
100
|
-
→ deriver.derivePrimitive(Int) → Lazy[Show[Int]]
|
|
101
|
-
→ BindingInstance(Binding.Primitive, Lazy[Show[Int]])
|
|
102
|
-
|
|
103
|
-
Step 3: Transform Record "Person"
|
|
104
|
-
→ deriver.deriveRecord(fields with transformed metadata)
|
|
105
|
-
→ Lazy[Show[Person]]
|
|
106
|
-
→ BindingInstance(Binding.Record, Lazy[Show[Person]])
|
|
107
|
-
|
|
108
|
-
PHASE 2: Extract Root Instance
|
|
109
|
-
┌──────────────────────────────────────────────────────────────────────────┐
|
|
110
|
-
│ transformedTree.metadata.instance.force │
|
|
111
|
-
│ │
|
|
112
|
-
│ This forces the Lazy[Show[Person]] at the root, which: │
|
|
113
|
-
│ - Creates the Show[Person] instance │
|
|
114
|
-
│ - Captures references to Lazy[Show[String]] and Lazy[Show[Int]] │
|
|
115
|
-
│ - Does NOT force the child instances yet (they remain lazy) │
|
|
116
|
-
└──────────────────────────────────────────────────────────────────────────┘
|
|
117
|
-
|
|
118
|
-
OUTPUT:
|
|
119
|
-
Show[Person] instance that can show any Person value
|
|
120
|
-
|
|
121
|
-
PHASE 3: Runtime Usage
|
|
122
|
-
┌──────────────────────────────────────────────────────────────────────────┐
|
|
123
|
-
│ When show.show(Person("Alice", 30)) is called: │
|
|
124
|
-
│ │
|
|
125
|
-
│ 1. Deconstruct Person into registers (name="Alice", age=30) │
|
|
126
|
-
│ 2. For each field, force its Lazy[Show] and call .show() │
|
|
127
|
-
│ - Force Lazy[Show[String]], call show("Alice") → "\"Alice\"" │
|
|
128
|
-
│ - Force Lazy[Show[Int]], call show(30) → "30" │
|
|
129
|
-
│ 3. Combine results → Person(name = "Alice", age = 30) │
|
|
130
|
-
└──────────────────────────────────────────────────────────────────────────┘
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## Step-by-Step Walkthrough
|
|
136
|
-
|
|
137
|
-
Let's trace through exactly what happens when we derive `Show[Person]`.
|
|
138
|
-
|
|
139
|
-
### Phase 1: Schema Definition
|
|
140
|
-
|
|
141
|
-
```scala
|
|
142
|
-
case class Person(name: String, age: Int)
|
|
143
|
-
implicit val schema: Schema[Person] = Schema.derived[Person]
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
At compile time, macros generate a `Reflect.Record` structure:
|
|
147
|
-
|
|
148
|
-
```scala
|
|
149
|
-
Reflect.Record[Binding, Person](
|
|
150
|
-
fields = Vector(
|
|
151
|
-
Term("name", Reflect.Primitive[Binding, String](PrimitiveType.String, ...)),
|
|
152
|
-
Term("age", Reflect.Primitive[Binding, Int](PrimitiveType.Int, ...))
|
|
153
|
-
),
|
|
154
|
-
typeId = TypeId[Person],
|
|
155
|
-
recordBinding = Binding.Record[Person](constructor, deconstructor)
|
|
156
|
-
)
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Phase 2: Derivation Starts
|
|
160
|
-
|
|
161
|
-
```scala
|
|
162
|
-
implicit val show: Show[Person] = schema.derive(DeriveShow)
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
This calls `DerivationBuilder.derive`, which:
|
|
166
|
-
|
|
167
|
-
1. **Prepares override maps** (for custom instances/modifiers)
|
|
168
|
-
2. **Calls `schema.reflect.transform(...)`** with a `ReflectTransformer`
|
|
169
|
-
|
|
170
|
-
### Phase 3: Tree Transformation
|
|
171
|
-
|
|
172
|
-
The transformation proceeds **bottom-up** (children before parents):
|
|
173
|
-
|
|
174
|
-
#### Step 3.1: Transform Field "name" (String primitive)
|
|
175
|
-
|
|
176
|
-
```
|
|
177
|
-
[Reflect.Primitive.transform] Transforming primitive 'String' at path: .name
|
|
178
|
-
[transformPrimitive] Calling deriver.derivePrimitive for 'String'
|
|
179
|
-
[derivePrimitive] Creating Lazy[Show[String]] (not yet evaluated)
|
|
180
|
-
[transformPrimitive] Created BindingInstance for 'String' with Lazy[TC] instance
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
The transformer:
|
|
184
|
-
1. Calls `deriver.derivePrimitive(PrimitiveType.String, ...)`
|
|
185
|
-
2. Gets back `Lazy[Show[String]]` (unevaluated)
|
|
186
|
-
3. Creates `BindingInstance(Binding.Primitive, Lazy[Show[String]])`
|
|
187
|
-
4. Returns `Reflect.Primitive[BindingInstance, String]`
|
|
188
|
-
|
|
189
|
-
#### Step 3.2: Transform Field "age" (Int primitive)
|
|
190
|
-
|
|
191
|
-
```
|
|
192
|
-
[Reflect.Primitive.transform] Transforming primitive 'Int' at path: .age
|
|
193
|
-
[transformPrimitive] Calling deriver.derivePrimitive for 'Int'
|
|
194
|
-
[derivePrimitive] Creating Lazy[Show[Int]] (not yet evaluated)
|
|
195
|
-
[transformPrimitive] Created BindingInstance for 'Int' with Lazy[TC] instance
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Same process for Int.
|
|
199
|
-
|
|
200
|
-
#### Step 3.3: Transform Record "Person"
|
|
201
|
-
|
|
202
|
-
```
|
|
203
|
-
[Reflect.Record.transform] All fields transformed, now transforming record 'Person'
|
|
204
|
-
[transformRecord] Calling deriver.deriveRecord for 'Person'
|
|
205
|
-
[deriveRecord] Creating Lazy[Show[Person]] (not yet evaluated)
|
|
206
|
-
[transformRecord] Created BindingInstance for 'Person' with Lazy[TC] instance
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
The transformer:
|
|
210
|
-
1. Receives the transformed fields (with `BindingInstance` metadata)
|
|
211
|
-
2. Calls `deriver.deriveRecord(transformedFields, ...)`
|
|
212
|
-
3. Gets back `Lazy[Show[Person]]` (unevaluated)
|
|
213
|
-
4. Creates `BindingInstance(Binding.Record, Lazy[Show[Person]])`
|
|
214
|
-
5. Returns `Reflect.Record[BindingInstance, Person]`
|
|
215
|
-
|
|
216
|
-
### Phase 4: Extract the Instance
|
|
217
|
-
|
|
218
|
-
```
|
|
219
|
-
[DerivationBuilder] STEP 3: Extracting the top-level instance
|
|
220
|
-
Calling .force on the Lazy[TC[A]] at the root...
|
|
221
|
-
|
|
222
|
-
[Lazy.force] Forcing unevaluated Lazy computation...
|
|
223
|
-
[deriveRecord] NOW EVALUATING: Creating Show instance for record type: Person
|
|
224
|
-
Step 1: Collecting Lazy[Show] instances for each field...
|
|
225
|
-
- Getting Lazy[Show] for field 'name' from transformed metadata
|
|
226
|
-
- Getting Lazy[Show] for field 'age' from transformed metadata
|
|
227
|
-
Step 2: Building Reflect.Record to compute register layout...
|
|
228
|
-
Step 3: Register layout computed.
|
|
229
|
-
[deriveRecord] Show instance for 'Person' created (field Show instances still lazy)
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
When `.force` is called on the root `Lazy[Show[Person]]`:
|
|
233
|
-
|
|
234
|
-
1. The deferred computation in `deriveRecord` finally executes
|
|
235
|
-
2. It retrieves the `Lazy[Show]` for each field (but doesn't force them!)
|
|
236
|
-
3. It builds the register layout for field access
|
|
237
|
-
4. It returns a `Show[Person]` instance
|
|
238
|
-
|
|
239
|
-
**Critical Point**: The field `Show` instances (`Lazy[Show[String]]`, `Lazy[Show[Int]]`) are **not forced yet**. They are captured by closure in the `Show[Person]` implementation.
|
|
240
|
-
|
|
241
|
-
### Phase 5: Using the Derived Instance
|
|
242
|
-
|
|
243
|
-
```scala
|
|
244
|
-
val result = Person.show.show(Person("Alice", 30))
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
```
|
|
248
|
-
[Show.show] Called for Person value
|
|
249
|
-
Step A: Created registers for deconstruction
|
|
250
|
-
Step B: Deconstructed record into registers
|
|
251
|
-
Step C: Building field strings (will force Lazy[Show] for each field)...
|
|
252
|
-
- Field 'name': forcing Lazy[Show] and calling show(Alice)
|
|
253
|
-
[Lazy.force] Forcing unevaluated Lazy computation...
|
|
254
|
-
[derivePrimitive] NOW EVALUATING: Creating Show instance for primitive type: String
|
|
255
|
-
- Field 'name' result: name = "Alice"
|
|
256
|
-
- Field 'age': forcing Lazy[Show] and calling show(30)
|
|
257
|
-
[Lazy.force] Forcing unevaluated Lazy computation...
|
|
258
|
-
[derivePrimitive] NOW EVALUATING: Creating Show instance for primitive type: Int
|
|
259
|
-
- Field 'age' result: age = 30
|
|
260
|
-
|
|
261
|
-
Final result: Person(name = "Alice", age = 30)
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
Only when we actually **use** the `Show[Person]` do the field instances get created:
|
|
265
|
-
|
|
266
|
-
1. Deconstruct `Person("Alice", 30)` into registers
|
|
267
|
-
2. For field "name":
|
|
268
|
-
- Force `Lazy[Show[String]]` → creates `Show[String]` instance
|
|
269
|
-
- Call `show("Alice")` → `"\"Alice\""`
|
|
270
|
-
3. For field "age":
|
|
271
|
-
- Force `Lazy[Show[Int]]` → creates `Show[Int]` instance
|
|
272
|
-
- Call `show(30)` → `"30"`
|
|
273
|
-
4. Combine: `Person(name = "Alice", age = 30)`
|
|
274
|
-
|
|
275
|
-
---
|
|
276
|
-
|
|
277
|
-
## Why Lazy Evaluation?
|
|
278
|
-
|
|
279
|
-
The `Lazy` wrapper is essential for handling **recursive types**:
|
|
280
|
-
|
|
281
|
-
```scala
|
|
282
|
-
case class Tree(value: Int, children: List[Tree])
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
The schema for `Tree` contains a reference back to itself:
|
|
286
|
-
|
|
287
|
-
```
|
|
288
|
-
Reflect.Record[Tree]
|
|
289
|
-
├── Term("value", Reflect.Primitive[Int])
|
|
290
|
-
└── Term("children", Reflect.Sequence[Tree] ← contains Tree!
|
|
291
|
-
└── Reflect.Deferred[Tree] ← lazy reference)
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
Without lazy evaluation:
|
|
295
|
-
- Deriving `Show[Tree]` would need `Show[List[Tree]]`
|
|
296
|
-
- Which needs `Show[Tree]`
|
|
297
|
-
- Which needs `Show[List[Tree]]`
|
|
298
|
-
- **Infinite loop!**
|
|
299
|
-
|
|
300
|
-
With lazy evaluation:
|
|
301
|
-
- Deriving `Show[Tree]` creates `Lazy[Show[Tree]]`
|
|
302
|
-
- `Lazy[Show[List[Tree]]]` captures a reference to the `Lazy[Show[Tree]]`
|
|
303
|
-
- No infinite loop because nothing is evaluated until `.force`
|
|
304
|
-
- The `Reflect.Deferred` uses caching to return the same instance
|
|
305
|
-
|
|
306
|
-
---
|
|
307
|
-
|
|
308
|
-
## Execution Trace Analysis
|
|
309
|
-
|
|
310
|
-
Here's the actual execution trace from running `DeriveShowApproachOne`. Each section is annotated to explain what's happening:
|
|
311
|
-
|
|
312
|
-
### Actual Output
|
|
313
|
-
|
|
314
|
-
```
|
|
315
|
-
================================================================================
|
|
316
|
-
Example 1: Simple Person Record with two primitive fields
|
|
317
|
-
================================================================================
|
|
318
|
-
|
|
319
|
-
------------------------------------------------------------
|
|
320
|
-
PHASE 1: Schema Definition
|
|
321
|
-
------------------------------------------------------------
|
|
322
|
-
Creating Schema[Person] via Schema.derived[Person]
|
|
323
|
-
This uses compile-time macros to generate a Reflect.Record structure
|
|
324
|
-
|
|
325
|
-
Schema created: Schema {
|
|
326
|
-
record Person {
|
|
327
|
-
name: String
|
|
328
|
-
age: Int
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
**What's happening**: At compile time, Scala macros inspect the `Person` case class and generate a `Reflect.Record` structure that describes its fields and types.
|
|
334
|
-
|
|
335
|
-
```
|
|
336
|
-
------------------------------------------------------------
|
|
337
|
-
PHASE 2: Type-Class Derivation (schema.derive(DeriveShow))
|
|
338
|
-
------------------------------------------------------------
|
|
339
|
-
This is where the magic happens!
|
|
340
|
-
The framework will:
|
|
341
|
-
1. Transform the schema tree, wrapping each node's Binding with BindingInstance
|
|
342
|
-
2. Create Lazy[Show[T]] instances for each schema node
|
|
343
|
-
3. Return the top-level Show[Person] instance
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
================================================================================
|
|
347
|
-
[DerivationBuilder.derive] Starting type-class derivation
|
|
348
|
-
================================================================================
|
|
349
|
-
Schema structure: Schema {
|
|
350
|
-
record Person {
|
|
351
|
-
name: String
|
|
352
|
-
age: Int
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
[DerivationBuilder] STEP 1: Starting schema tree transformation
|
|
357
|
-
Purpose: Transform each node from Reflect[Binding, A] to Reflect[BindingInstance, A]
|
|
358
|
-
This wraps each Binding with a Lazy[TC[A]] instance for that node
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
**What's happening**: The `DerivationBuilder` begins the transformation process. The goal is to convert the schema tree from `Reflect[Binding, A]` (just runtime bindings) to `Reflect[BindingInstance, A]` (bindings + derived instances).
|
|
362
|
-
|
|
363
|
-
```
|
|
364
|
-
[Lazy.force] Forcing unevaluated Lazy computation...
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
**What's happening**: The transformation itself is lazy! This first `.force` kicks off the tree traversal.
|
|
368
|
-
|
|
369
|
-
```
|
|
370
|
-
[Reflect.Record.transform] Starting transformation for 'Person' at path: .
|
|
371
|
-
- This record has 2 fields: name, age
|
|
372
|
-
- Will first transform all child fields (bottom-up), then transform the record itself
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
**What's happening**: We start at the root (Person), but we must transform children first. This is the **bottom-up** traversal pattern.
|
|
376
|
-
|
|
377
|
-
```
|
|
378
|
-
[Reflect.Primitive.transform] Transforming primitive 'Int' at path: .age
|
|
379
|
-
[transformPrimitive] Calling deriver.derivePrimitive for 'Int' at path: .age
|
|
380
|
-
[derivePrimitive] Creating Lazy[Show[Int]] (not yet evaluated)
|
|
381
|
-
- PrimitiveType: Int
|
|
382
|
-
- This Lazy will be forced later when the Show instance is actually needed
|
|
383
|
-
[transformPrimitive] Created BindingInstance for 'Int' with Lazy[TC] instance
|
|
384
|
-
[Reflect.Primitive.transform] Completed transformation for 'Int'
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
**What's happening**: The `age` field (Int primitive) is transformed first:
|
|
388
|
-
1. `deriver.derivePrimitive` is called, which returns a `Lazy[Show[Int]]`
|
|
389
|
-
2. A `BindingInstance` is created wrapping the original `Binding.Primitive` with this `Lazy[Show[Int]]`
|
|
390
|
-
3. The `Lazy` is **not forced yet** - the actual `Show[Int]` instance hasn't been created!
|
|
391
|
-
|
|
392
|
-
```
|
|
393
|
-
[Reflect.Primitive.transform] Transforming primitive 'String' at path: .name
|
|
394
|
-
[transformPrimitive] Calling deriver.derivePrimitive for 'String' at path: .name
|
|
395
|
-
[derivePrimitive] Creating Lazy[Show[String]] (not yet evaluated)
|
|
396
|
-
- PrimitiveType: String
|
|
397
|
-
- This Lazy will be forced later when the Show instance is actually needed
|
|
398
|
-
[transformPrimitive] Created BindingInstance for 'String' with Lazy[TC] instance
|
|
399
|
-
[Reflect.Primitive.transform] Completed transformation for 'String'
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
**What's happening**: Same process for the `name` field (String primitive). Now both children are transformed.
|
|
403
|
-
|
|
404
|
-
```
|
|
405
|
-
[Reflect.Record.transform] All fields transformed, now transforming record 'Person'
|
|
406
|
-
[transformRecord] Calling deriver.deriveRecord for 'Person' at path: .
|
|
407
|
-
[deriveRecord] Creating Lazy[Show[Person]] (not yet evaluated)
|
|
408
|
-
- Number of fields: 2
|
|
409
|
-
- Field names: name, age
|
|
410
|
-
- This Lazy will be forced later when the Show instance is actually needed
|
|
411
|
-
[transformRecord] Created BindingInstance for 'Person' with Lazy[TC] instance
|
|
412
|
-
[Reflect.Record.transform] Completed transformation for 'Person'
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
**What's happening**: Now the parent (Person record) can be transformed:
|
|
416
|
-
1. It receives the transformed fields (with `BindingInstance` metadata)
|
|
417
|
-
2. `deriver.deriveRecord` is called, returning `Lazy[Show[Person]]`
|
|
418
|
-
3. A `BindingInstance` wraps `Binding.Record` with `Lazy[Show[Person]]`
|
|
419
|
-
|
|
420
|
-
```
|
|
421
|
-
[DerivationBuilder] STEP 2: Schema transformation complete
|
|
422
|
-
The entire schema tree has been transformed.
|
|
423
|
-
Each node now has a BindingInstance containing:
|
|
424
|
-
- The original Binding (for runtime operations)
|
|
425
|
-
- A Lazy[TC[A]] (the derived type-class instance)
|
|
426
|
-
|
|
427
|
-
[DerivationBuilder] STEP 3: Extracting the top-level instance
|
|
428
|
-
Calling .force on the Lazy[TC[A]] at the root of the transformed tree...
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
**What's happening**: The tree transformation is complete. Now we need to extract the actual `Show[Person]` instance from the root node.
|
|
432
|
-
|
|
433
|
-
```
|
|
434
|
-
[deriveRecord] NOW EVALUATING: Creating Show instance for record type: Person
|
|
435
|
-
Step 1: Collecting Lazy[Show] instances for each field...
|
|
436
|
-
- Getting Lazy[Show] for field 'name' from transformed metadata
|
|
437
|
-
(This retrieves the Lazy[Show] that was created during transformation)
|
|
438
|
-
[HasInstance.instance] Retrieving Lazy[TC] from BindingInstance
|
|
439
|
-
- Binding type: Primitive
|
|
440
|
-
- Instance evaluated: false
|
|
441
|
-
Got Lazy[Show] for 'name' (not yet forced)
|
|
442
|
-
- Getting Lazy[Show] for field 'age' from transformed metadata
|
|
443
|
-
(This retrieves the Lazy[Show] that was created during transformation)
|
|
444
|
-
[HasInstance.instance] Retrieving Lazy[TC] from BindingInstance
|
|
445
|
-
- Binding type: Primitive
|
|
446
|
-
- Instance evaluated: false
|
|
447
|
-
Got Lazy[Show] for 'age' (not yet forced)
|
|
448
|
-
Step 2: Building Reflect.Record to compute register layout...
|
|
449
|
-
Step 3: Register layout computed. UsedRegisters: 17179869185
|
|
450
|
-
[deriveRecord] Show instance for 'Person' created (field Show instances still lazy)
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
**What's happening**: This is crucial! When we force `Lazy[Show[Person]]`:
|
|
454
|
-
1. The `deriveRecord` deferred code finally runs
|
|
455
|
-
2. It retrieves the `Lazy[Show]` for each field from the transformed metadata
|
|
456
|
-
3. **It does NOT force those lazy instances** - they remain unevaluated
|
|
457
|
-
4. The `Show[Person]` is created, capturing references to the lazy field instances
|
|
458
|
-
|
|
459
|
-
```
|
|
460
|
-
------------------------------------------------------------
|
|
461
|
-
PHASE 3: Using the Derived Show Instance
|
|
462
|
-
------------------------------------------------------------
|
|
463
|
-
Now calling personShow.show(Person("Alice", 30))
|
|
464
|
-
This will force the Lazy[Show] instances as needed...
|
|
465
|
-
|
|
466
|
-
[Show.show] Called for Person value
|
|
467
|
-
Step A: Created registers for deconstruction
|
|
468
|
-
Step B: Deconstructed record into registers
|
|
469
|
-
Step C: Building field strings (will force Lazy[Show] for each field)...
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
**What's happening**: Now we actually use the `Show[Person]` instance:
|
|
473
|
-
1. Registers are allocated for holding field values
|
|
474
|
-
2. The `Person("Alice", 30)` value is deconstructed into registers (name="Alice", age=30)
|
|
475
|
-
|
|
476
|
-
```
|
|
477
|
-
- Field 'name': forcing Lazy[Show] and calling show(Alice)
|
|
478
|
-
[Lazy.force] Forcing unevaluated Lazy computation...
|
|
479
|
-
[derivePrimitive] NOW EVALUATING: Creating Show instance for primitive type: String
|
|
480
|
-
- Field 'name' result: name = "Alice"
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
**What's happening**: For the first field:
|
|
484
|
-
1. The `Lazy[Show[String]]` is finally forced
|
|
485
|
-
2. The `derivePrimitive` deferred code runs, creating the actual `Show[String]`
|
|
486
|
-
3. `show("Alice")` is called, returning `"\"Alice\""`
|
|
487
|
-
|
|
488
|
-
```
|
|
489
|
-
- Field 'age': forcing Lazy[Show] and calling show(30)
|
|
490
|
-
[Lazy.force] Forcing unevaluated Lazy computation...
|
|
491
|
-
[derivePrimitive] NOW EVALUATING: Creating Show instance for primitive type: Int
|
|
492
|
-
- Field 'age' result: age = 30
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
**What's happening**: Same for the second field:
|
|
496
|
-
1. `Lazy[Show[Int]]` is forced
|
|
497
|
-
2. `Show[Int]` is created
|
|
498
|
-
3. `show(30)` returns `"30"`
|
|
499
|
-
|
|
500
|
-
```
|
|
501
|
-
============================================================
|
|
502
|
-
FINAL RESULT: Person(name = "Alice", age = 30)
|
|
503
|
-
============================================================
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
**What's happening**: The field strings are combined into the final result.
|
|
507
|
-
|
|
508
|
-
---
|
|
509
|
-
|
|
510
|
-
## Visual Representation of the Transformation
|
|
511
|
-
|
|
512
|
-
### Before Transformation
|
|
513
|
-
|
|
514
|
-
```
|
|
515
|
-
Schema[Person]
|
|
516
|
-
│
|
|
517
|
-
▼
|
|
518
|
-
┌─────────────────────────────────────────────────────────────────────┐
|
|
519
|
-
│ Reflect.Record[Binding, Person] │
|
|
520
|
-
│ ┌─────────────────────────────────────────────────────────────┐ │
|
|
521
|
-
│ │ metadata: Binding.Record[Person] │ │
|
|
522
|
-
│ │ - constructor: (String, Int) => Person │ │
|
|
523
|
-
│ │ - deconstructor: Person => (String, Int) │ │
|
|
524
|
-
│ └─────────────────────────────────────────────────────────────┘ │
|
|
525
|
-
│ │
|
|
526
|
-
│ fields: │
|
|
527
|
-
│ ├── Term("name", ─────────────────────────────────────────────┐ │
|
|
528
|
-
│ │ │ Reflect.Primitive[Binding, String] │ │
|
|
529
|
-
│ │ │ ┌─────────────────────────────────────────────────┐ │ │
|
|
530
|
-
│ │ │ │ metadata: Binding.Primitive │ │ │
|
|
531
|
-
│ │ │ │ primitiveType: PrimitiveType.String │ │ │
|
|
532
|
-
│ │ │ └─────────────────────────────────────────────────┘ │ │
|
|
533
|
-
│ │ └─────────────────────────────────────────────────────────┘ │
|
|
534
|
-
│ │ │
|
|
535
|
-
│ └── Term("age", ──────────────────────────────────────────────┐ │
|
|
536
|
-
│ │ Reflect.Primitive[Binding, Int] │ │
|
|
537
|
-
│ │ ┌─────────────────────────────────────────────────┐ │ │
|
|
538
|
-
│ │ │ metadata: Binding.Primitive │ │ │
|
|
539
|
-
│ │ │ primitiveType: PrimitiveType.Int │ │ │
|
|
540
|
-
│ │ └─────────────────────────────────────────────────┘ │ │
|
|
541
|
-
│ └─────────────────────────────────────────────────────────┘ │
|
|
542
|
-
└─────────────────────────────────────────────────────────────────────┘
|
|
543
|
-
```
|
|
544
|
-
|
|
545
|
-
### After Transformation
|
|
546
|
-
|
|
547
|
-
```
|
|
548
|
-
Reflect.Record[BindingInstance[Show], Person]
|
|
549
|
-
│
|
|
550
|
-
▼
|
|
551
|
-
┌─────────────────────────────────────────────────────────────────────┐
|
|
552
|
-
│ Reflect.Record[BindingInstance[Show], Person] │
|
|
553
|
-
│ ┌─────────────────────────────────────────────────────────────┐ │
|
|
554
|
-
│ │ metadata: BindingInstance[Show, Record, Person] │ │
|
|
555
|
-
│ │ ├── binding: Binding.Record[Person] │ │
|
|
556
|
-
│ │ └── instance: Lazy[Show[Person]] ◄─── FORCED (evaluated) │ │
|
|
557
|
-
│ └─────────────────────────────────────────────────────────────┘ │
|
|
558
|
-
│ │
|
|
559
|
-
│ fields: │
|
|
560
|
-
│ ├── Term("name", ─────────────────────────────────────────────┐ │
|
|
561
|
-
│ │ │ Reflect.Primitive[BindingInstance[Show], String] │ │
|
|
562
|
-
│ │ │ ┌─────────────────────────────────────────────────┐ │ │
|
|
563
|
-
│ │ │ │ metadata: BindingInstance[Show, Primitive, Str] │ │ │
|
|
564
|
-
│ │ │ │ ├── binding: Binding.Primitive │ │ │
|
|
565
|
-
│ │ │ │ └── instance: Lazy[Show[String]] ◄── NOT YET! │ │ │
|
|
566
|
-
│ │ │ └─────────────────────────────────────────────────┘ │ │
|
|
567
|
-
│ │ └─────────────────────────────────────────────────────────┘ │
|
|
568
|
-
│ │ │
|
|
569
|
-
│ └── Term("age", ──────────────────────────────────────────────┐ │
|
|
570
|
-
│ │ Reflect.Primitive[BindingInstance[Show], Int] │ │
|
|
571
|
-
│ │ ┌─────────────────────────────────────────────────┐ │ │
|
|
572
|
-
│ │ │ metadata: BindingInstance[Show, Primitive, Int] │ │ │
|
|
573
|
-
│ │ │ ├── binding: Binding.Primitive │ │ │
|
|
574
|
-
│ │ │ └── instance: Lazy[Show[Int]] ◄─── NOT YET! │ │ │
|
|
575
|
-
│ │ └─────────────────────────────────────────────────┘ │ │
|
|
576
|
-
│ └─────────────────────────────────────────────────────────┘ │
|
|
577
|
-
└─────────────────────────────────────────────────────────────────────┘
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
### Key Observation
|
|
581
|
-
|
|
582
|
-
Notice that after transformation:
|
|
583
|
-
- The root `Lazy[Show[Person]]` is **FORCED** (evaluated)
|
|
584
|
-
- The child `Lazy[Show[String]]` and `Lazy[Show[Int]]` are **NOT YET** evaluated
|
|
585
|
-
- They will only be evaluated when `Show[Person].show(...)` is actually called
|
|
586
|
-
|
|
587
|
-
This lazy evaluation strategy is what enables:
|
|
588
|
-
1. **Efficient derivation**: Only create instances that are actually used
|
|
589
|
-
2. **Recursive type support**: Self-referential types don't cause infinite loops
|
|
590
|
-
3. **Memory efficiency**: Instances are created on-demand
|
|
591
|
-
|
|
592
|
-
---
|
|
593
|
-
|
|
594
|
-
## Summary
|
|
595
|
-
|
|
596
|
-
The type-class derivation process in ZIO Blocks:
|
|
597
|
-
|
|
598
|
-
1. **Transforms the schema tree bottom-up**, visiting primitives/leaves before records/variants
|
|
599
|
-
2. **Wraps each node's Binding** with a `BindingInstance` containing a `Lazy[TC[A]]`
|
|
600
|
-
3. **Uses lazy evaluation** to defer actual instance creation until needed
|
|
601
|
-
4. **Forces only what's necessary** - the root instance is forced, but children remain lazy until used
|
|
602
|
-
5. **Handles recursion** through `Lazy` and `Reflect.Deferred` with caching
|
|
603
|
-
|
|
604
|
-
This design provides:
|
|
605
|
-
- **Efficiency**: Only creates instances that are actually used
|
|
606
|
-
- **Safety**: Handles recursive types without stack overflow
|
|
607
|
-
- **Composability**: Each node's derivation is independent and can be overridden
|
|
608
|
-
|
|
609
|
-
---
|
|
610
|
-
|
|
611
|
-
## Key Files in the Codebase
|
|
612
|
-
|
|
613
|
-
| File | Purpose |
|
|
614
|
-
|------|---------|
|
|
615
|
-
| `schema/shared/src/main/scala/zio/blocks/schema/derive/DerivationBuilder.scala` | Orchestrates the derivation process |
|
|
616
|
-
| `schema/shared/src/main/scala/zio/blocks/schema/derive/Deriver.scala` | The trait you implement to create a new deriver |
|
|
617
|
-
| `schema/shared/src/main/scala/zio/blocks/schema/derive/BindingInstance.scala` | Container for Binding + Lazy instance |
|
|
618
|
-
| `schema/shared/src/main/scala/zio/blocks/schema/Reflect.scala` | Schema tree nodes with transform methods |
|
|
619
|
-
| `schema/shared/src/main/scala/zio/blocks/schema/Lazy.scala` | Trampolined lazy evaluation monad |
|
|
620
|
-
| `zio-blocks-examples/src/main/scala/typeclassderivation/DeriveShowApproachOne.scala` | Example with detailed logging |
|
|
621
|
-
|
|
622
|
-
---
|
|
623
|
-
|
|
624
|
-
## Running the Example
|
|
625
|
-
|
|
626
|
-
To see the full execution trace yourself:
|
|
627
|
-
|
|
628
|
-
```bash
|
|
629
|
-
sbt "examples/runMain typeclassderivation.DeriveShowApproachOne"
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
This will print the detailed step-by-step trace shown in this document, helping you understand exactly what happens during type-class derivation.
|