@zio.dev/zio-blocks 0.0.1

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.
@@ -0,0 +1,632 @@
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.