@zio.dev/zio-blocks 0.0.26 → 0.0.28

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.
@@ -128,7 +128,7 @@ Given a `Schema[A]`, you can call the `derive` method to get an instance of the
128
128
 
129
129
  ```scala
130
130
  case class Schema[A](reflect: Reflect.Bound[A]) {
131
- def derive[TC[_]](deriver: Deriver[TC]): TC[A] = ???
131
+ def derive[D, TC[_]](d: D)(implicit ev: Derivable[D, TC]): TC[A] = ???
132
132
  }
133
133
  ```
134
134
 
@@ -158,13 +158,7 @@ val result: Either[SchemaError, Person] =
158
158
  )
159
159
  ```
160
160
 
161
- There is another overloaded version of the `Schema#derive` method that takes a `Format` instead of a `Deriver`:
162
-
163
- ```scala
164
- case class Schema[A](reflect: Reflect.Bound[A]) {
165
- def derive[F <: codec.Format](format: F): format.TypeClass[A] = derive(format.deriver)
166
- }
167
- ```
161
+ There is a `Derivable` type class that enables seamless overloading between `Deriver[TC]` and `Format` arguments, allowing the same `derive` method to work with both:
168
162
 
169
163
  For example, by calling `Person.schema.derive(JsonFormat)`, we can derive a `JsonCodec[Person]` instance:
170
164
 
@@ -224,47 +218,39 @@ object DeriveShow extends Deriver[Show] {
224
218
  modifiers: Seq[Modifier.Reflect],
225
219
  defaultValue: Option[A],
226
220
  examples: Seq[A]
227
- )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[A]] =
221
+ )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[A]] = {
222
+ // Pre-compute structural setup outside Lazy
223
+ val fieldNames = fields.map(_.name)
224
+ // Cast fields to use Binding as F (we are going to create Reflect.Record with Binding as F)
225
+ val recordFields = fields.asInstanceOf[IndexedSeq[Term[Binding, A, ?]]]
226
+ // Cast to Binding.Record to access constructor/deconstructor
227
+ val recordBinding = binding.asInstanceOf[Binding.Record[A]]
228
+ // Build a Reflect.Record to get access to the computed registers for each field
229
+ val recordReflect = new Reflect.Record[Binding, A](recordFields, typeId, recordBinding, doc, modifiers)
228
230
  Lazy {
229
- // Collecting Lazy[Show] instances for each field from the transformed metadata
230
- val fieldShowInstances: IndexedSeq[(String, Lazy[Show[Any]])] = fields.map { field =>
231
- val fieldName = field.name
232
- // Get the Lazy[Show] instance for this field's type, but we won't force it yet
233
- // We'll force it later when we actually need to show a value of this field
234
- val fieldShowInstance = D.instance(field.value.metadata).asInstanceOf[Lazy[Show[Any]]]
235
- (fieldName, fieldShowInstance)
236
- }
237
-
238
- // Cast fields to use Binding as F (we are going to create Reflect.Record with Binding as F)
239
- val recordFields = fields.asInstanceOf[IndexedSeq[Term[Binding, A, ?]]]
240
-
241
- // Cast to Binding.Record to access constructor/deconstructor
242
- val recordBinding = binding.asInstanceOf[Binding.Record[A]]
243
-
244
- // Build a Reflect.Record to get access to the computed registers for each field
245
- val recordReflect = new Reflect.Record[Binding, A](recordFields, typeId, recordBinding, doc, modifiers)
246
-
247
231
  new Show[A] {
232
+ // Defer child-instance resolution to first show() call via lazy val.
233
+ // For recursive types (e.g. case class Tree(children: List[Tree])), accessing
234
+ // field.value.metadata during derivation would re-enter a Deferred node that is
235
+ // still initializing, causing an infinite loop. The lazy val here ensures access
236
+ // only happens after the framework has finished deriving all instances.
237
+ private lazy val resolvedShows: IndexedSeq[Show[Any]] =
238
+ fields.map(field => D.instance(field.value.metadata).asInstanceOf[Lazy[Show[Any]]].force)
248
239
  def show(value: A): String = {
249
-
250
240
  // Create registers with space for all used registers to hold deconstructed field values
251
241
  val registers = Registers(recordReflect.usedRegisters)
252
-
253
242
  // Deconstruct field values of the record into the registers
254
243
  recordBinding.deconstructor.deconstruct(registers, RegisterOffset.Zero, value)
255
-
256
244
  // Build string representations for all fields
257
245
  val fieldStrings = fields.indices.map { i =>
258
- val (fieldName, showInstanceLazy) = fieldShowInstances(i)
259
- val fieldValue = recordReflect.registers(i).get(registers, RegisterOffset.Zero)
260
- val result = s"$fieldName = ${showInstanceLazy.force.show(fieldValue)}"
261
- result
246
+ val fieldValue = recordReflect.registers(i).get(registers, RegisterOffset.Zero)
247
+ s"${fieldNames(i)} = ${resolvedShows(i).show(fieldValue)}"
262
248
  }
263
-
264
249
  s"${typeId.name}(${fieldStrings.mkString(", ")})"
265
250
  }
266
251
  }
267
252
  }
253
+ }
268
254
 
269
255
  override def deriveVariant[F[_, _], A](
270
256
  cases: IndexedSeq[Term[F, A, ?]],
@@ -274,29 +260,28 @@ object DeriveShow extends Deriver[Show] {
274
260
  modifiers: Seq[Modifier.Reflect],
275
261
  defaultValue: Option[A],
276
262
  examples: Seq[A]
277
- )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[A]] = Lazy {
278
- // Get Show instances for all cases LAZILY
279
- val caseShowInstances: IndexedSeq[Lazy[Show[Any]]] = cases.map { case_ =>
263
+ )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[A]] = {
264
+ // Collect the Lazy[Show] references for each case outside the Lazy block.
265
+ // Capturing Lazy refs is safe here; we only .force them later inside the lazy val.
266
+ val caseShowLazies: IndexedSeq[Lazy[Show[Any]]] = cases.map { case_ =>
280
267
  D.instance(case_.value.metadata).asInstanceOf[Lazy[Show[Any]]]
281
268
  }
282
-
283
269
  // Cast binding to Binding.Variant to access discriminator and matchers
284
270
  val variantBinding = binding.asInstanceOf[Binding.Variant[A]]
285
- val discriminator = variantBinding.discriminator
286
- val matchers = variantBinding.matchers
287
-
288
- new Show[A] {
289
- // Implement show by using discriminator and matchers to find the right case
290
- // The `value` parameter is of type A (the variant type), e.g. an Option[Int] value
291
- def show(value: A): String = {
292
- // Use discriminator to determine which case this value belongs to
293
- val caseIndex = discriminator.discriminate(value)
294
-
295
- // Use matcher to downcast to the specific case type
296
- val caseValue = matchers(caseIndex).downcastOrNull(value)
297
-
298
- // Just delegate to the case's Show instance - it already knows its own name
299
- caseShowInstances(caseIndex).force.show(caseValue)
271
+ Lazy {
272
+ new Show[A] {
273
+ // Force child instances lazily — same recursive-safety rationale as deriveRecord
274
+ private lazy val resolvedShows: IndexedSeq[Show[Any]] = caseShowLazies.map(_.force)
275
+ // Implement show by using discriminator and matchers to find the right case
276
+ // The `value` parameter is of type A (the variant type), e.g. a Shape value
277
+ def show(value: A): String = {
278
+ // Use discriminator to determine which case this value belongs to
279
+ val caseIndex = variantBinding.discriminator.discriminate(value)
280
+ // Use matcher to downcast to the specific case type
281
+ val caseValue = variantBinding.matchers(caseIndex).downcastOrNull(value)
282
+ // Delegate to the case's Show instance — it already knows its own name
283
+ resolvedShows(caseIndex).show(caseValue)
284
+ }
300
285
  }
301
286
  }
302
287
  }
@@ -309,21 +294,19 @@ object DeriveShow extends Deriver[Show] {
309
294
  modifiers: Seq[Modifier.Reflect],
310
295
  defaultValue: Option[C[A]],
311
296
  examples: Seq[C[A]]
312
- )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[C[A]]] = Lazy {
313
- // Get Show instance for element type LAZILY
314
- val elementShowLazy: Lazy[Show[A]] = D.instance(element.metadata)
315
-
297
+ )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[C[A]]] = {
316
298
  // Cast binding to Binding.Seq to access the deconstructor
317
- val seqBinding = binding.asInstanceOf[Binding.Seq[C, A]]
318
- val deconstructor = seqBinding.deconstructor
319
-
320
- new Show[C[A]] {
321
- def show(value: C[A]): String = {
322
- // Use deconstructor to iterate over elements
323
- val iterator = deconstructor.deconstruct(value)
324
- // Force the element Show instance only when actually showing
325
- val elements = iterator.map(elem => elementShowLazy.force.show(elem)).mkString(", ")
326
- s"[$elements]"
299
+ val deconstructor = binding.asInstanceOf[Binding.Seq[C, A]].deconstructor
300
+ // Sequences are structurally non-recursive, so we can use monadic .map composition.
301
+ // instance(...).map { elementShow => ... } returns a Lazy that, when forced, builds
302
+ // a Show[C[A]] with elementShow already resolved — no .force needed at show()-time.
303
+ D.instance(element.metadata).map { elementShow =>
304
+ new Show[C[A]] {
305
+ def show(value: C[A]): String = {
306
+ // Use deconstructor to iterate over elements and show each one
307
+ val elements = deconstructor.deconstruct(value).map(elementShow.show)
308
+ s"[${elements.mkString(", ")}]"
309
+ }
327
310
  }
328
311
  }
329
312
  }
@@ -337,26 +320,22 @@ object DeriveShow extends Deriver[Show] {
337
320
  modifiers: Seq[Modifier.Reflect],
338
321
  defaultValue: Option[M[K, V]],
339
322
  examples: Seq[M[K, V]]
340
- )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[M[K, V]]] = Lazy {
341
- // Get Show instances for key and value types LAZILY
342
- val keyShowLazy: Lazy[Show[K]] = D.instance(key.metadata)
343
- val valueShowLazy: Lazy[Show[V]] = D.instance(value.metadata)
344
-
323
+ )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[M[K, V]]] = {
345
324
  // Cast binding to Binding.Map to access the deconstructor
346
- val mapBinding = binding.asInstanceOf[Binding.Map[M, K, V]]
347
- val deconstructor = mapBinding.deconstructor
348
-
349
- new Show[M[K, V]] {
350
- def show(m: M[K, V]): String = {
351
- // Use deconstructor to iterate over key-value pairs
352
- val iterator = deconstructor.deconstruct(m)
353
- // Force the Show instances only when actually showing
354
- val entries = iterator.map { kv =>
355
- val k = deconstructor.getKey(kv)
356
- val v = deconstructor.getValue(kv)
357
- s"${keyShowLazy.force.show(k)} -> ${valueShowLazy.force.show(v)}"
358
- }.mkString(", ")
359
- s"Map($entries)"
325
+ val deconstructor = binding.asInstanceOf[Binding.Map[M, K, V]].deconstructor
326
+ // Maps are non-recursive: use .zip to pair the two child Lazy instances, then .map
327
+ // to build the Show[M[K,V]] with both keyShow and valueShow already resolved.
328
+ D.instance(key.metadata).zip(D.instance(value.metadata)).map { case (keyShow, valueShow) =>
329
+ new Show[M[K, V]] {
330
+ def show(m: M[K, V]): String = {
331
+ // Use deconstructor to iterate over key-value pairs
332
+ val entries = deconstructor.deconstruct(m).map { kv =>
333
+ val k = deconstructor.getKey(kv)
334
+ val v = deconstructor.getValue(kv)
335
+ s"${keyShow.show(k)} -> ${valueShow.show(v)}"
336
+ }.mkString(", ")
337
+ s"Map($entries)"
338
+ }
360
339
  }
361
340
  }
362
341
  }
@@ -406,17 +385,18 @@ object DeriveShow extends Deriver[Show] {
406
385
  modifiers: Seq[Modifier.Reflect],
407
386
  defaultValue: Option[A],
408
387
  examples: Seq[A]
409
- )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[A]] = Lazy {
410
- // Get Show instance for the wrapped (underlying) type B LAZILY
411
- val wrappedShowLazy: Lazy[Show[B]] = D.instance(wrapped.metadata)
412
-
413
- // Cast binding to Binding.Wrapper to access unwrap function
388
+ )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[A]] = {
389
+ // Cast binding to Binding.Wrapper to access the unwrap function
414
390
  val wrapperBinding = binding.asInstanceOf[Binding.Wrapper[A, B]]
415
-
416
- new Show[A] {
417
- def show(value: A): String = {
418
- val unwrapped = wrapperBinding.unwrap(value)
419
- s"${typeId.name}(${wrappedShowLazy.force.show(unwrapped)})"
391
+ // Wrappers are non-recursive: use .map so wrappedShow is already resolved
392
+ // when show() is called — no .force needed.
393
+ D.instance(wrapped.metadata).map { wrappedShow =>
394
+ new Show[A] {
395
+ def show(value: A): String = {
396
+ // Unwrap the value to access the underlying type B, then delegate to its Show
397
+ val unwrapped = wrapperBinding.unwrap(value)
398
+ s"${typeId.name}(${wrappedShow.show(unwrapped)})"
399
+ }
420
400
  }
421
401
  }
422
402
  }
@@ -469,57 +449,50 @@ def deriveRecord[F[_, _], A](
469
449
  modifiers: Seq[Modifier.Reflect],
470
450
  defaultValue: Option[A],
471
451
  examples: Seq[A]
472
- )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[A]] =
452
+ )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[A]] = {
453
+ // Pre-compute structural setup outside Lazy — none of this touches Deferred nodes
454
+ val fieldNames = fields.map(_.name)
455
+ // Cast fields to use Binding as F (we are going to create Reflect.Record with Binding as F)
456
+ val recordFields = fields.asInstanceOf[IndexedSeq[Term[Binding, A, ?]]]
457
+ // Cast to Binding.Record to access constructor/deconstructor
458
+ val recordBinding = binding.asInstanceOf[Binding.Record[A]]
459
+ // Build a Reflect.Record to get access to the computed registers for each field
460
+ val recordReflect = new Reflect.Record[Binding, A](recordFields, typeId, recordBinding, doc, modifiers)
473
461
  Lazy {
474
- // Collecting Lazy[Show] instances for each field from the transformed metadata
475
- val fieldShowInstances: IndexedSeq[(String, Lazy[Show[Any]])] = fields.map { field =>
476
- val fieldName = field.name
477
- // Get the Lazy[Show] instance for this field's type, but we won't force it yet
478
- // We'll force it later when we actually need to show a value of this field
479
- val fieldShowInstance = D.instance(field.value.metadata).asInstanceOf[Lazy[Show[Any]]]
480
- (fieldName, fieldShowInstance)
481
- }
482
-
483
- // Cast fields to use Binding as F (we are going to create Reflect.Record with Binding as F)
484
- val recordFields = fields.asInstanceOf[IndexedSeq[Term[Binding, A, ?]]]
485
-
486
- // Cast to Binding.Record to access constructor/deconstructor
487
- val recordBinding = binding.asInstanceOf[Binding.Record[A]]
488
-
489
- // Build a Reflect.Record to get access to the computed registers for each field
490
- val recordReflect = new Reflect.Record[Binding, A](recordFields, typeId, recordBinding, doc, modifiers)
491
-
492
462
  new Show[A] {
463
+ // Defer child-instance resolution to first show() call via lazy val.
464
+ // For recursive types (e.g. case class Tree(children: List[Tree])), accessing
465
+ // field.value.metadata during derivation would re-enter a Deferred node that is
466
+ // still initialising, causing an infinite loop. The lazy val here ensures access
467
+ // only happens after the framework has finished deriving all instances.
468
+ private lazy val resolvedShows: IndexedSeq[Show[Any]] =
469
+ fields.map(field => D.instance(field.value.metadata).asInstanceOf[Lazy[Show[Any]]].force)
493
470
  def show(value: A): String = {
494
-
495
471
  // Create registers with space for all used registers to hold deconstructed field values
496
472
  val registers = Registers(recordReflect.usedRegisters)
497
-
498
473
  // Deconstruct field values of the record into the registers
499
474
  recordBinding.deconstructor.deconstruct(registers, RegisterOffset.Zero, value)
500
-
501
475
  // Build string representations for all fields
502
476
  val fieldStrings = fields.indices.map { i =>
503
- val (fieldName, showInstanceLazy) = fieldShowInstances(i)
504
- val fieldValue = recordReflect.registers(i).get(registers, RegisterOffset.Zero)
505
- val result = s"$fieldName = ${showInstanceLazy.force.show(fieldValue)}"
506
- result
477
+ val fieldValue = recordReflect.registers(i).get(registers, RegisterOffset.Zero)
478
+ s"${fieldNames(i)} = ${resolvedShows(i).show(fieldValue)}"
507
479
  }
508
-
509
480
  s"${typeId.name}(${fieldStrings.mkString(", ")})"
510
481
  }
511
482
  }
512
483
  }
484
+ }
513
485
  ```
514
486
 
515
487
  The `deriveRecord` method demonstrates derivation mechanics for record types such as case classes and tuples. To derive the type class for a record type, we follow these steps:
516
- 1. First, we extract the type class instances for each field of the record.
517
- 2. Second, we have to deconstruct the record value at runtime to access individual field values.
518
- 3. Third, we assemble the final string representation of the record by combining field names and their corresponding representations using the extracted type class instances.
488
+ 1. First, we pre-compute structural setup (field names, `Reflect.Record`, binding casts) outside the `Lazy` block — none of this accesses `Deferred` nodes.
489
+ 2. Second, inside `Lazy`, we build a `new Show[A]` with a `private lazy val resolvedShows` that resolves child instances on demand.
490
+ 3. Third, when `show()` is first called, `resolvedShows` forces the child `Lazy[Show]` instances and then deconstructs the record value into its individual fields.
491
+ 4. Finally, we assemble the string representation by combining field names and their representations.
519
492
 
520
- During the first step, the method gathers `Lazy[Show]` instances for each field by calling `D.instance(field.value.metadata)`. This method extracts the derived type class instance for the field's type from the transformed schema metadata. Again, the transformed metadata contains `Reflect[BindingInstance[TC, _, _], A]` nodes, where each node has a `BindingInstance` that bundles together the structural binding and the derived type class instance. By calling `D.instance`, we retrieve the `Lazy[Show]` instance for each field's type.
493
+ During the first step, the structural setup is computed outside the `Lazy` block so the `Lazy` thunk itself is lightweight. This separation is important because structural types like `Reflect.Record` don't access `Deferred` schema nodes that might not yet be initialized.
521
494
 
522
- These instances are wrapped in `Lazy` to support recursive data types—if a `Person` contains a `List[Person]`, we need to delay forcing the inner `Show[Person]` until runtime to avoid infinite loops during derivation.
495
+ The key insight is the `private lazy val resolvedShows` inside the `new Show[A]` class. It gathers `Lazy[Show]` instances for each field by calling `D.instance(field.value.metadata)`, then immediately forces them. These calls happen lazily — only on the first invocation of `show()` — which is safe because by that time the framework has completed derivation and all `Lazy` values are fully memoized. Without this deferral, recursive types like `case class Tree(value: Int, children: List[Tree])` would cause an infinite loop: deriving `Show[Tree]` requires `Show[List[Tree]]`, which requires `Show[Tree]` again.
523
496
 
524
497
  Our goal is to build a `String` representation of the record in the format `TypeName(field1 = value1, field2 = value2, ...)`. To achieve this, we need to access the individual field values of the record at runtime. To do this, we have to deconstruct the record value, which is given to the `show(value: A)` method, into its individual fields.
525
498
 
@@ -527,9 +500,9 @@ To deconstruct the record, we use the `Binding.Record[A]` that was provided as a
527
500
 
528
501
  Now we are ready to deconstruct the `A` value, using the `Binding.Record#deconstructor.deconstruct(registers, RegisterOffset.Zero, value)` call, which extracts the field values of the record into this register buffer in a single pass. Now the field values are stored in `registers`.
529
502
 
530
- The next question is how we can access the field values from the registers? The `Reflect.Record` we built earlier also computes the register layout for each field, which allows us to retrieve each field value from the appropriate register slot using `recordReflect.registers(i).get(registers, RegisterOffset.Zero)`. This call accesses the `i`-th field's value from the registers based on the register layout computed by `Reflect.Record`.
503
+ The next question is how we can access the field values from the registers? The `Reflect.Record` we built earlier also computes the register layout for each field, which allows us to retrieve each field value from the appropriate register slot using `recordReflect.registers(i).get(registers, RegisterOffset.Zero)`. This call accesses the `i`-th field's value from the registers based on the register layout computed by `Reflect.Record`.
531
504
 
532
- Finally, we can iterate through each field, retrieve its value from the registers, force the corresponding `Lazy[Show]` instance for that field's type, and format the result as `fieldName = fieldValue`. The output assembles into the familiar `TypeName(field1 = value1, field2 = value2)` representation.
505
+ Finally, we iterate through each field, retrieve its value from the registers, look up the already-resolved `Show` instance for that field's type from `resolvedShows`, and format the result as `fieldName = fieldValue`. The output assembles into the familiar `TypeName(field1 = value1, field2 = value2)` representation.
533
506
 
534
507
  ### Variant Derivation
535
508
 
@@ -544,33 +517,36 @@ def deriveVariant[F[_, _], A](
544
517
  modifiers: Seq[Modifier.Reflect],
545
518
  defaultValue: Option[A],
546
519
  examples: Seq[A]
547
- )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[A]] = Lazy {
548
- // Get Show instances for all cases LAZILY
549
- val caseShowInstances: IndexedSeq[Lazy[Show[Any]]] = cases.map { case_ =>
520
+ )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[A]] = {
521
+ // Collect the Lazy[Show] references for each case outside the Lazy block.
522
+ // Capturing Lazy refs is safe here; we only .force them later inside the lazy val.
523
+ val caseShowLazies: IndexedSeq[Lazy[Show[Any]]] = cases.map { case_ =>
550
524
  D.instance(case_.value.metadata).asInstanceOf[Lazy[Show[Any]]]
551
525
  }
552
526
  // Cast binding to Binding.Variant to access discriminator and matchers
553
527
  val variantBinding = binding.asInstanceOf[Binding.Variant[A]]
554
- val discriminator = variantBinding.discriminator
555
- val matchers = variantBinding.matchers
556
- new Show[A] {
557
- // Implement show by using discriminator and matchers to find the right case
558
- // The `value` parameter is of type A (the variant type), e.g. an Option[Int] value
559
- def show(value: A): String = {
560
- // Use discriminator to determine which case this value belongs to
561
- val caseIndex = discriminator.discriminate(value)
562
- // Use matcher to downcast to the specific case type
563
- val caseValue = matchers(caseIndex).downcastOrNull(value)
564
- // Just delegate to the case's Show instance - it already knows its own name
565
- caseShowInstances(caseIndex).force.show(caseValue)
528
+ Lazy {
529
+ new Show[A] {
530
+ // Force child instances lazily — same recursive-safety rationale as deriveRecord
531
+ private lazy val resolvedShows: IndexedSeq[Show[Any]] = caseShowLazies.map(_.force)
532
+ // Implement show by using discriminator and matchers to find the right case
533
+ // The `value` parameter is of type A (the variant type), e.g. a Shape value
534
+ def show(value: A): String = {
535
+ // Use discriminator to determine which case this value belongs to
536
+ val caseIndex = variantBinding.discriminator.discriminate(value)
537
+ // Use matcher to downcast to the specific case type
538
+ val caseValue = variantBinding.matchers(caseIndex).downcastOrNull(value)
539
+ // Delegate to the case's Show instance — it already knows its own name
540
+ resolvedShows(caseIndex).show(caseValue)
541
+ }
566
542
  }
567
543
  }
568
544
  }
569
545
  ```
570
546
 
571
- The derivation process for variants is similar to records, but instead of fields, we have cases. We extract the type class instances for each case, and at runtime we use the discriminator to determine which case the value belongs to. Then we use the matcher to downcast the value to the specific case type.
547
+ The derivation process for variants is similar to records, but instead of fields, we have cases. The `Lazy[Show]` references for all cases are collected outside the `Lazy` block — this is safe because it only captures references without forcing them. Inside `Lazy`, a `private lazy val resolvedShows` forces all the child instances on the first `show()` call, applying the same recursive-safety guarantee as in `deriveRecord`.
572
548
 
573
- Finally, we extract the corresponding type class instance for that case by applying the case index to the indexed sequence of type class instances. Now we have the correct type class instance for the specific case, wrapped in a `Lazy` data type. We force the lazy wrapper to retrieve the actual type class instance, and then we call the `show` method on that case value to get the string representation.
549
+ At runtime, we use the discriminator to determine which case the value belongs to, and then the matcher to downcast the value to the specific case type. Finally, we look up the already-resolved `Show` instance for that case from `resolvedShows` and delegate to it. Each case's `Show` instance already knows how to format itself (including its own type name), so no additional formatting is needed at the variant level.
574
550
 
575
551
  ### Sequence Derivation
576
552
 
@@ -585,25 +561,25 @@ def deriveSequence[F[_, _], C[_], A](
585
561
  modifiers: Seq[Modifier.Reflect],
586
562
  defaultValue: Option[C[A]],
587
563
  examples: Seq[C[A]]
588
- )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[C[A]]] = Lazy {
589
- // Get Show instance for element type (lazily)
590
- val elementShowLazy: Lazy[Show[A]] = D.instance(element.metadata)
564
+ )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[C[A]]] = {
591
565
  // Cast binding to Binding.Seq to access the deconstructor
592
- val seqBinding = binding.asInstanceOf[Binding.Seq[C, A]]
593
- val deconstructor = seqBinding.deconstructor
594
- new Show[C[A]] {
595
- def show(value: C[A]): String = {
596
- // Use the deconstructor to iterate over elements
597
- val iterator = deconstructor.deconstruct(value)
598
- // Force the element Show instance only when actually showing
599
- val elements = iterator.map(elem => elementShowLazy.force.show(elem)).mkString(", ")
600
- s"[$elements]"
566
+ val deconstructor = binding.asInstanceOf[Binding.Seq[C, A]].deconstructor
567
+ // Sequences are structurally non-recursive, so we can use monadic .map composition.
568
+ // instance(...).map { elementShow => ... } returns a Lazy that, when forced, builds
569
+ // a Show[C[A]] with elementShow already resolved — no .force needed at show()-time.
570
+ D.instance(element.metadata).map { elementShow =>
571
+ new Show[C[A]] {
572
+ def show(value: C[A]): String = {
573
+ // Use deconstructor to iterate over elements and show each one
574
+ val elements = deconstructor.deconstruct(value).map(elementShow.show)
575
+ s"[${elements.mkString(", ")}]"
576
+ }
601
577
  }
602
578
  }
603
579
  }
604
580
  ```
605
581
 
606
- The derivation process for sequences is straightforward. We extract the type class instance for the element type, and at runtime we use the deconstructor to iterate over the elements of the sequence. For each element, we force the `Lazy[Show[A]]` instance to get the actual `Show[A]` instance, and then call `show` on each element to get its string representation. Finally, we combine all element representations into a single string that represents the entire sequence.
582
+ The derivation process for sequences is straightforward. Because sequences are structurally non-recursive, we can use monadic `Lazy` composition via `.map` instead of the `private lazy val` pattern needed for records and variants. `D.instance(element.metadata).map { elementShow => ... }` returns a `Lazy[Show[C[A]]]` that, when forced, produces a `Show[C[A]]` with `elementShow` already resolved — no explicit `.force` is needed inside `show()`. At runtime, we use the deconstructor to iterate over the elements of the sequence and call `elementShow.show` on each one. Finally, we combine all element representations into a bracketed string.
607
583
 
608
584
  ### Map Derivation
609
585
 
@@ -619,32 +595,28 @@ def deriveMap[F[_, _], M[_, _], K, V](
619
595
  modifiers: Seq[Modifier.Reflect],
620
596
  defaultValue: Option[M[K, V]],
621
597
  examples: Seq[M[K, V]]
622
- )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[M[K, V]]] = Lazy {
623
- // Get Show instances for key and value types LAZILY
624
- val keyShowLazy: Lazy[Show[K]] = D.instance(key.metadata)
625
- val valueShowLazy: Lazy[Show[V]] = D.instance(value.metadata)
626
-
598
+ )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[M[K, V]]] = {
627
599
  // Cast binding to Binding.Map to access the deconstructor
628
- val mapBinding = binding.asInstanceOf[Binding.Map[M, K, V]]
629
- val deconstructor = mapBinding.deconstructor
630
-
631
- new Show[M[K, V]] {
632
- def show(m: M[K, V]): String = {
633
- // Use deconstructor to iterate over key-value pairs
634
- val iterator = deconstructor.deconstruct(m)
635
- // Force the Show instances only when actually showing
636
- val entries = iterator.map { kv =>
637
- val k = deconstructor.getKey(kv)
638
- val v = deconstructor.getValue(kv)
639
- s"${keyShowLazy.force.show(k)} -> ${valueShowLazy.force.show(v)}"
640
- }.mkString(", ")
641
- s"Map($entries)"
600
+ val deconstructor = binding.asInstanceOf[Binding.Map[M, K, V]].deconstructor
601
+ // Maps are non-recursive: use .zip to pair the two child Lazy instances, then .map
602
+ // to build the Show[M[K,V]] with both keyShow and valueShow already resolved.
603
+ D.instance(key.metadata).zip(D.instance(value.metadata)).map { case (keyShow, valueShow) =>
604
+ new Show[M[K, V]] {
605
+ def show(m: M[K, V]): String = {
606
+ // Use deconstructor to iterate over key-value pairs
607
+ val entries = deconstructor.deconstruct(m).map { kv =>
608
+ val k = deconstructor.getKey(kv)
609
+ val v = deconstructor.getValue(kv)
610
+ s"${keyShow.show(k)} -> ${valueShow.show(v)}"
611
+ }.mkString(", ")
612
+ s"Map($entries)"
613
+ }
642
614
  }
643
615
  }
644
616
  }
645
617
  ```
646
618
 
647
- The derivation process for maps is similar to sequences, but we have to handle both keys and values. We extract the type class instances for the key and value types, and at runtime we use the deconstructor to iterate over the key-value pairs of the map. For each pair, we force the `Lazy[Show[K]]` and `Lazy[Show[V]]` instances to get the actual `Show[K]` and `Show[V]` instances, and then call `show` on both the key and value to get their string representations. Finally, we combine all entries into a single string that represents the entire map.
619
+ The derivation process for maps is similar to sequences, but we have two child instances to compose. Maps are non-recursive, so we use `.zip` to pair the two child `Lazy` instances into a single `Lazy[(Show[K], Show[V])]`, then `.map` to build the `Show[M[K,V]]` with both `keyShow` and `valueShow` already resolved. At runtime, we use the deconstructor to iterate over the key-value pairs of the map, calling `keyShow.show` and `valueShow.show` on each pair. No explicit `.force` is needed since `.map` already handles forcing. Finally, we combine all entries into a `Map(...)` string.
648
620
 
649
621
  ### Dynamic Derivation
650
622
 
@@ -700,23 +672,24 @@ def deriveWrapper[F[_, _], A, B](
700
672
  modifiers: Seq[Modifier.Reflect],
701
673
  defaultValue: Option[A],
702
674
  examples: Seq[A]
703
- )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[A]] = Lazy {
704
- // Get Show instance for the wrapped (underlying) type B LAZILY
705
- val wrappedShowLazy: Lazy[Show[B]] = D.instance(wrapped.metadata)
706
-
707
- // Cast binding to Binding.Wrapper to access unwrap function
675
+ )(implicit F: HasBinding[F], D: DeriveShow.HasInstance[F]): Lazy[Show[A]] = {
676
+ // Cast binding to Binding.Wrapper to access the unwrap function
708
677
  val wrapperBinding = binding.asInstanceOf[Binding.Wrapper[A, B]]
709
-
710
- new Show[A] {
711
- def show(value: A): String = {
712
- val unwrapped = wrapperBinding.unwrap(value)
713
- s"${typeId.name}(${wrappedShowLazy.force.show(unwrapped)})"
678
+ // Wrappers are non-recursive: use .map so wrappedShow is already resolved
679
+ // when show() is called — no .force needed.
680
+ D.instance(wrapped.metadata).map { wrappedShow =>
681
+ new Show[A] {
682
+ def show(value: A): String = {
683
+ // Unwrap the value to access the underlying type B, then delegate to its Show
684
+ val unwrapped = wrapperBinding.unwrap(value)
685
+ s"${typeId.name}(${wrappedShow.show(unwrapped)})"
686
+ }
714
687
  }
715
688
  }
716
689
  }
717
690
  ```
718
691
 
719
- The derivation process for wrapper types involves unwrapping the value to access the underlying type. We extract the type class instance for the wrapped type, and at runtime we use the `unwrap` function from the binding to get the underlying value, then show it using its type class instance.
692
+ The derivation process for wrapper types involves unwrapping the value to access the underlying type. Wrappers are non-recursive, so we use `.map` composition: `D.instance(wrapped.metadata).map { wrappedShow => ... }` returns a `Lazy[Show[A]]` that, when forced, produces a `Show[A]` with `wrappedShow` already resolved. At runtime, we use the `unwrap` function from the binding to retrieve the underlying `B` value and delegate to `wrappedShow.show` — no explicit `.force` is needed.
720
693
 
721
694
  ### Example Usages
722
695
 
@@ -945,34 +918,35 @@ object DeriveGen extends Deriver[Gen] {
945
918
  modifiers: Seq[Modifier.Reflect],
946
919
  defaultValue: Option[A],
947
920
  examples: Seq[A]
948
- )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[A]] =
921
+ )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[A]] = {
922
+ // Pre-compute structural setup outside Lazy — none of this touches Deferred nodes
923
+ // Build Reflect.Record to access registers and constructor
924
+ val recordFields = fields.asInstanceOf[IndexedSeq[Term[Binding, A, ?]]]
925
+ val recordBinding = binding.asInstanceOf[Binding.Record[A]]
926
+ val recordReflect = new Reflect.Record[Binding, A](recordFields, typeId, recordBinding, doc, modifiers)
949
927
  Lazy {
950
- // Get Gen instances for each field
951
- val fieldGens: IndexedSeq[Lazy[Gen[Any]]] = fields.map { field =>
952
- D.instance(field.value.metadata).asInstanceOf[Lazy[Gen[Any]]]
953
- }
954
-
955
- // Build Reflect.Record to access registers and constructor
956
- val recordFields = fields.asInstanceOf[IndexedSeq[Term[Binding, A, ?]]]
957
- val recordBinding = binding.asInstanceOf[Binding.Record[A]]
958
- val recordReflect = new Reflect.Record[Binding, A](recordFields, typeId, recordBinding, doc, modifiers)
959
-
960
928
  new Gen[A] {
929
+ // Defer child-instance resolution to first generate() call via lazy val.
930
+ // For recursive types (e.g. case class Tree(children: List[Tree])), accessing
931
+ // field.value.metadata during derivation would re-enter a Deferred node that is
932
+ // still initialising, causing an infinite loop. The lazy val here ensures access
933
+ // only happens after the framework has finished deriving all instances.
934
+ private lazy val resolvedGens: IndexedSeq[Gen[Any]] =
935
+ fields.map(field => D.instance(field.value.metadata).asInstanceOf[Lazy[Gen[Any]]].force)
961
936
  def generate(random: Random): A = {
962
937
  // Create registers to hold field values
963
938
  val registers = Registers(recordReflect.usedRegisters)
964
-
965
939
  // Generate each field and store in registers
966
940
  fields.indices.foreach { i =>
967
- val value = fieldGens(i).force.generate(random)
941
+ val value = resolvedGens(i).generate(random)
968
942
  recordReflect.registers(i).set(registers, RegisterOffset.Zero, value)
969
943
  }
970
-
971
944
  // Construct the record from registers
972
945
  recordBinding.constructor.construct(registers, RegisterOffset.Zero)
973
946
  }
974
947
  }
975
948
  }
949
+ }
976
950
 
977
951
  /**
978
952
  * Strategy:
@@ -988,17 +962,21 @@ object DeriveGen extends Deriver[Gen] {
988
962
  modifiers: Seq[Modifier.Reflect],
989
963
  defaultValue: Option[A],
990
964
  examples: Seq[A]
991
- )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[A]] = Lazy {
992
- // Get Gen instances for all cases
993
- val caseGens: IndexedSeq[Lazy[Gen[A]]] = cases.map { c =>
965
+ )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[A]] = {
966
+ // Collect the Lazy[Gen] references for each case outside the Lazy block.
967
+ // Capturing Lazy refs is safe here; we only .force them later inside the lazy val.
968
+ val caseGenLazies: IndexedSeq[Lazy[Gen[A]]] = cases.map { c =>
994
969
  D.instance(c.value.metadata).asInstanceOf[Lazy[Gen[A]]]
995
970
  }
996
-
997
- new Gen[A] {
998
- def generate(random: Random): A = {
999
- // Pick a random case and generate its value
1000
- val caseIndex = random.nextInt(cases.length)
1001
- caseGens(caseIndex).force.generate(random)
971
+ Lazy {
972
+ new Gen[A] {
973
+ // Force child instances lazily — same recursive-safety rationale as deriveRecord
974
+ private lazy val resolvedGens: IndexedSeq[Gen[A]] = caseGenLazies.map(_.force)
975
+ def generate(random: Random): A = {
976
+ // Pick a random case and generate its value
977
+ val caseIndex = random.nextInt(cases.length)
978
+ resolvedGens(caseIndex).generate(random)
979
+ }
1002
980
  }
1003
981
  }
1004
982
  }
@@ -1017,24 +995,29 @@ object DeriveGen extends Deriver[Gen] {
1017
995
  modifiers: Seq[Modifier.Reflect],
1018
996
  defaultValue: Option[C[A]],
1019
997
  examples: Seq[C[A]]
1020
- )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[C[A]]] = Lazy {
1021
- val elementGen = D.instance(element.metadata)
1022
- val seqBinding = binding.asInstanceOf[Binding.Seq[C, A]]
1023
- val constructor = seqBinding.constructor
1024
-
1025
- new Gen[C[A]] {
1026
- def generate(random: Random): C[A] = {
1027
- val length = random.nextInt(6) // 0 to 5 elements
1028
- implicit val ct: scala.reflect.ClassTag[A] = scala.reflect.ClassTag.Any.asInstanceOf[scala.reflect.ClassTag[A]]
1029
-
1030
- if (length == 0) {
1031
- constructor.empty[A]
1032
- } else {
1033
- val builder = constructor.newBuilder[A](length)
1034
- (0 until length).foreach { _ =>
1035
- constructor.add(builder, elementGen.force.generate(random))
998
+ )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[C[A]]] = {
999
+ // Cast binding to Binding.Seq to access the constructor
1000
+ val constructor = binding.asInstanceOf[Binding.Seq[C, A]].constructor
1001
+ val elemClassTag = element.typeId.classTag.asInstanceOf[scala.reflect.ClassTag[A]]
1002
+ // Sequences are structurally non-recursive, so we can use monadic .map composition.
1003
+ // instance(...).map { elementGen => ... } returns a Lazy that, when forced, builds
1004
+ // a Gen[C[A]] with elementGen already resolved — no .force needed at generate()-time.
1005
+ D.instance(element.metadata).map { elementGen =>
1006
+ new Gen[C[A]] {
1007
+ def generate(random: Random): C[A] = {
1008
+ val length = random.nextInt(6) // 0 to 5 elements
1009
+ implicit val ct: scala.reflect.ClassTag[A] = elemClassTag
1010
+
1011
+ if (length == 0) {
1012
+ constructor.empty[A]
1013
+ } else {
1014
+ // Build the collection by generating each element and adding it to the builder
1015
+ val builder = constructor.newBuilder[A](length)
1016
+ (0 until length).foreach { _ =>
1017
+ constructor.add(builder, elementGen.generate(random))
1018
+ }
1019
+ constructor.result(builder)
1036
1020
  }
1037
- constructor.result(builder)
1038
1021
  }
1039
1022
  }
1040
1023
  }
@@ -1055,24 +1038,26 @@ object DeriveGen extends Deriver[Gen] {
1055
1038
  modifiers: Seq[Modifier.Reflect],
1056
1039
  defaultValue: Option[M[K, V]],
1057
1040
  examples: Seq[M[K, V]]
1058
- )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[M[K, V]]] = Lazy {
1059
- val keyGen = D.instance(key.metadata)
1060
- val valueGen = D.instance(value.metadata)
1061
- val mapBinding = binding.asInstanceOf[Binding.Map[M, K, V]]
1062
- val constructor = mapBinding.constructor
1063
-
1064
- new Gen[M[K, V]] {
1065
- def generate(random: Random): M[K, V] = {
1066
- val size = random.nextInt(6) // 0 to 5 entries
1067
-
1068
- if (size == 0) {
1069
- constructor.emptyObject[K, V]
1070
- } else {
1071
- val builder = constructor.newObjectBuilder[K, V](size)
1072
- (0 until size).foreach { _ =>
1073
- constructor.addObject(builder, keyGen.force.generate(random), valueGen.force.generate(random))
1041
+ )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[M[K, V]]] = {
1042
+ // Cast binding to Binding.Map to access the constructor
1043
+ val constructor = binding.asInstanceOf[Binding.Map[M, K, V]].constructor
1044
+ // Maps are non-recursive: use .zip to pair the two child Lazy instances, then .map
1045
+ // to build the Gen[M[K,V]] with both keyGen and valueGen already resolved.
1046
+ D.instance(key.metadata).zip(D.instance(value.metadata)).map { case (keyGen, valueGen) =>
1047
+ new Gen[M[K, V]] {
1048
+ def generate(random: Random): M[K, V] = {
1049
+ val size = random.nextInt(6) // 0 to 5 entries
1050
+
1051
+ if (size == 0) {
1052
+ constructor.emptyObject[K, V]
1053
+ } else {
1054
+ // Build the map by generating each key-value pair and adding it to the builder
1055
+ val builder = constructor.newObjectBuilder[K, V](size)
1056
+ (0 until size).foreach { _ =>
1057
+ constructor.addObject(builder, keyGen.generate(random), valueGen.generate(random))
1058
+ }
1059
+ constructor.resultObject(builder)
1074
1060
  }
1075
- constructor.resultObject(builder)
1076
1061
  }
1077
1062
  }
1078
1063
  }
@@ -1144,13 +1129,17 @@ object DeriveGen extends Deriver[Gen] {
1144
1129
  modifiers: Seq[Modifier.Reflect],
1145
1130
  defaultValue: Option[A],
1146
1131
  examples: Seq[A]
1147
- )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[A]] = Lazy {
1148
- val wrappedGen = D.instance(wrapped.metadata)
1132
+ )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[A]] = {
1133
+ // Cast binding to Binding.Wrapper to access the wrap function
1149
1134
  val wrapperBinding = binding.asInstanceOf[Binding.Wrapper[A, B]]
1150
-
1151
- new Gen[A] {
1152
- def generate(random: Random): A =
1153
- wrapperBinding.wrap(wrappedGen.force.generate(random))
1135
+ // Wrappers are non-recursive: use .map so wrappedGen is already resolved
1136
+ // when generate() is called — no .force needed.
1137
+ D.instance(wrapped.metadata).map { wrappedGen =>
1138
+ new Gen[A] {
1139
+ def generate(random: Random): A =
1140
+ // Generate a value of the underlying type B and wrap it into A
1141
+ wrapperBinding.wrap(wrappedGen.generate(random))
1142
+ }
1154
1143
  }
1155
1144
  }
1156
1145
  }
@@ -1203,37 +1192,38 @@ def deriveRecord[F[_, _], A](
1203
1192
  modifiers: Seq[Modifier.Reflect],
1204
1193
  defaultValue: Option[A],
1205
1194
  examples: Seq[A]
1206
- )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[A]] =
1195
+ )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[A]] = {
1196
+ // Pre-compute structural setup outside Lazy — none of this touches Deferred nodes
1197
+ // Build Reflect.Record to access registers and constructor
1198
+ val recordFields = fields.asInstanceOf[IndexedSeq[Term[Binding, A, ?]]]
1199
+ val recordBinding = binding.asInstanceOf[Binding.Record[A]]
1200
+ val recordReflect = new Reflect.Record[Binding, A](recordFields, typeId, recordBinding, doc, modifiers)
1207
1201
  Lazy {
1208
- // Get Gen instances for each field
1209
- val fieldGens: IndexedSeq[Lazy[Gen[Any]]] = fields.map { field =>
1210
- D.instance(field.value.metadata).asInstanceOf[Lazy[Gen[Any]]]
1211
- }
1212
-
1213
- // Build Reflect.Record to access registers and constructor
1214
- val recordFields = fields.asInstanceOf[IndexedSeq[Term[Binding, A, ?]]]
1215
- val recordBinding = binding.asInstanceOf[Binding.Record[A]]
1216
- val recordReflect = new Reflect.Record[Binding, A](recordFields, typeId, recordBinding, doc, modifiers)
1217
-
1218
1202
  new Gen[A] {
1203
+ // Defer child-instance resolution to first generate() call via lazy val.
1204
+ // For recursive types (e.g. case class Tree(children: List[Tree])), accessing
1205
+ // field.value.metadata during derivation would re-enter a Deferred node that is
1206
+ // still initialising, causing an infinite loop. The lazy val here ensures access
1207
+ // only happens after the framework has finished deriving all instances.
1208
+ private lazy val resolvedGens: IndexedSeq[Gen[Any]] =
1209
+ fields.map(field => D.instance(field.value.metadata).asInstanceOf[Lazy[Gen[Any]]].force)
1219
1210
  def generate(random: Random): A = {
1220
1211
  // Create registers to hold field values
1221
1212
  val registers = Registers(recordReflect.usedRegisters)
1222
-
1223
1213
  // Generate each field and store in registers
1224
1214
  fields.indices.foreach { i =>
1225
- val value = fieldGens(i).force.generate(random)
1215
+ val value = resolvedGens(i).generate(random)
1226
1216
  recordReflect.registers(i).set(registers, RegisterOffset.Zero, value)
1227
1217
  }
1228
-
1229
1218
  // Construct the record from registers
1230
1219
  recordBinding.constructor.construct(registers, RegisterOffset.Zero)
1231
1220
  }
1232
1221
  }
1233
1222
  }
1223
+ }
1234
1224
  ```
1235
1225
 
1236
- As shown above, the implementation of the `deriveRecord` method for `Gen` is structurally similar to the `deriveRecord` method used in `Show` derivation. The primary difference is the data flow: instead of deconstructing an existing record to access its fields, we generate random values for each field. We then use `Register#set` to store these values in the registers before invoking the `constructor` from the `Binding` to create an instance of type `A`.
1226
+ As shown above, the implementation of the `deriveRecord` method for `Gen` follows the same structure as its `Show` counterpart. The structural setup (field casts, `Reflect.Record`) is done outside `Lazy` to keep the thunk lightweight, and `resolvedGens` is a `private lazy val` that defers child-instance resolution until the first `generate()` call for recursive-type safety. The primary difference from `Show` is the data flow: instead of deconstructing an existing record to read its fields, we generate random values for each field via `resolvedGens(i).generate(random)`, store them in registers using `Register#set`, and then invoke the `constructor` from the `Binding` to create an instance of type `A`.
1237
1227
 
1238
1228
  ### Variant Derivation
1239
1229
 
@@ -1248,23 +1238,27 @@ def deriveVariant[F[_, _], A](
1248
1238
  modifiers: Seq[Modifier.Reflect],
1249
1239
  defaultValue: Option[A],
1250
1240
  examples: Seq[A]
1251
- )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[A]] = Lazy {
1252
- // Get Gen instances for all cases
1253
- val caseGens: IndexedSeq[Lazy[Gen[A]]] = cases.map { c =>
1241
+ )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[A]] = {
1242
+ // Collect the Lazy[Gen] references for each case outside the Lazy block.
1243
+ // Capturing Lazy refs is safe here; we only .force them later inside the lazy val.
1244
+ val caseGenLazies: IndexedSeq[Lazy[Gen[A]]] = cases.map { c =>
1254
1245
  D.instance(c.value.metadata).asInstanceOf[Lazy[Gen[A]]]
1255
1246
  }
1256
-
1257
- new Gen[A] {
1258
- def generate(random: Random): A = {
1259
- // Pick a random case and generate its value
1260
- val caseIndex = random.nextInt(cases.length)
1261
- caseGens(caseIndex).force.generate(random)
1247
+ Lazy {
1248
+ new Gen[A] {
1249
+ // Force child instances lazily — same recursive-safety rationale as deriveRecord
1250
+ private lazy val resolvedGens: IndexedSeq[Gen[A]] = caseGenLazies.map(_.force)
1251
+ def generate(random: Random): A = {
1252
+ // Pick a random case and generate its value
1253
+ val caseIndex = random.nextInt(cases.length)
1254
+ resolvedGens(caseIndex).generate(random)
1255
+ }
1262
1256
  }
1263
1257
  }
1264
1258
  }
1265
1259
  ```
1266
1260
 
1267
- The derivation process for `Gen` variants is simpler than for the record case because we don't need to worry about registers or constructors. Instead, we simply need to randomly select one of the type class instances for the cases and generate a value for that case.
1261
+ The derivation process for `Gen` variants applies the same deferral pattern as `deriveRecord`. The `caseGenLazies` are collected outside the `Lazy` block — capturing `Lazy` references is safe — and `resolvedGens` forces them lazily on the first `generate()` call. At runtime, we randomly select one of the resolved `Gen` instances and call `generate` on it. This is simpler than the record case because there are no registers or constructors involved — each case's `Gen` already knows how to produce a complete value of its type.
1268
1262
 
1269
1263
  ### Sequence Derivation
1270
1264
 
@@ -1279,31 +1273,36 @@ def deriveSequence[F[_, _], C[_], A](
1279
1273
  modifiers: Seq[Modifier.Reflect],
1280
1274
  defaultValue: Option[C[A]],
1281
1275
  examples: Seq[C[A]]
1282
- )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[C[A]]] = Lazy {
1283
- val elementGen = D.instance(element.metadata)
1284
- val seqBinding = binding.asInstanceOf[Binding.Seq[C, A]]
1285
- val constructor = seqBinding.constructor
1286
-
1287
- new Gen[C[A]] {
1288
- def generate(random: Random): C[A] = {
1289
- val length = random.nextInt(6) // 0 to 5 elements
1290
- implicit val ct: scala.reflect.ClassTag[A] = scala.reflect.ClassTag.Any.asInstanceOf[scala.reflect.ClassTag[A]]
1291
-
1292
- if (length == 0) {
1293
- constructor.empty[A]
1294
- } else {
1295
- val builder = constructor.newBuilder[A](length)
1296
- (0 until length).foreach { _ =>
1297
- constructor.add(builder, elementGen.force.generate(random))
1276
+ )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[C[A]]] = {
1277
+ // Cast binding to Binding.Seq to access the constructor
1278
+ val constructor = binding.asInstanceOf[Binding.Seq[C, A]].constructor
1279
+ val elemClassTag = element.typeId.classTag.asInstanceOf[scala.reflect.ClassTag[A]]
1280
+ // Sequences are structurally non-recursive, so we can use monadic .map composition.
1281
+ // instance(...).map { elementGen => ... } returns a Lazy that, when forced, builds
1282
+ // a Gen[C[A]] with elementGen already resolved — no .force needed at generate()-time.
1283
+ D.instance(element.metadata).map { elementGen =>
1284
+ new Gen[C[A]] {
1285
+ def generate(random: Random): C[A] = {
1286
+ val length = random.nextInt(6) // 0 to 5 elements
1287
+ implicit val ct: scala.reflect.ClassTag[A] = elemClassTag
1288
+
1289
+ if (length == 0) {
1290
+ constructor.empty[A]
1291
+ } else {
1292
+ // Build the collection by generating each element and adding it to the builder
1293
+ val builder = constructor.newBuilder[A](length)
1294
+ (0 until length).foreach { _ =>
1295
+ constructor.add(builder, elementGen.generate(random))
1296
+ }
1297
+ constructor.result(builder)
1298
1298
  }
1299
- constructor.result(builder)
1300
1299
  }
1301
1300
  }
1302
1301
  }
1303
1302
  }
1304
1303
  ```
1305
1304
 
1306
- A sequence is an object that contains multiple elements of the same type. To derive a `Gen` instance for a sequence, we first need to retrieve the `Gen` instance for the element type. Then, at runtime, we generate a random length for the sequence (e.g., between 0 and 5). Based on this length, we either return an empty sequence using `constructor.empty` or create a new builder using `constructor.newBuilder`. We then generate random values for each element using the element's type class instance and add them to the builder using `constructor.add`. Finally, we call `constructor.result` to build the final sequence object.
1305
+ A sequence is an object that contains multiple elements of the same type. Because sequences are non-recursive, we use `.map` composition: `D.instance(element.metadata).map { elementGen => ... }` returns a `Lazy[Gen[C[A]]]` that, when forced, builds a `Gen[C[A]]` with `elementGen` already resolved — no explicit `.force` is needed inside `generate()`. At runtime, we generate a random length (0–5). For an empty length, we return `constructor.empty`. Otherwise, we create a new builder using `constructor.newBuilder`, generate random element values with `elementGen.generate(random)`, add them with `constructor.add`, and finalise with `constructor.result`.
1307
1306
 
1308
1307
  ### Map Derivation
1309
1308
 
@@ -1319,31 +1318,33 @@ def deriveMap[F[_, _], M[_, _], K, V](
1319
1318
  modifiers: Seq[Modifier.Reflect],
1320
1319
  defaultValue: Option[M[K, V]],
1321
1320
  examples: Seq[M[K, V]]
1322
- )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[M[K, V]]] = Lazy {
1323
- val keyGen = D.instance(key.metadata)
1324
- val valueGen = D.instance(value.metadata)
1325
- val mapBinding = binding.asInstanceOf[Binding.Map[M, K, V]]
1326
- val constructor = mapBinding.constructor
1327
-
1328
- new Gen[M[K, V]] {
1329
- def generate(random: Random): M[K, V] = {
1330
- val size = random.nextInt(6) // 0 to 5 entries
1331
-
1332
- if (size == 0) {
1333
- constructor.emptyObject[K, V]
1334
- } else {
1335
- val builder = constructor.newObjectBuilder[K, V](size)
1336
- (0 until size).foreach { _ =>
1337
- constructor.addObject(builder, keyGen.force.generate(random), valueGen.force.generate(random))
1321
+ )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[M[K, V]]] = {
1322
+ // Cast binding to Binding.Map to access the constructor
1323
+ val constructor = binding.asInstanceOf[Binding.Map[M, K, V]].constructor
1324
+ // Maps are non-recursive: use .zip to pair the two child Lazy instances, then .map
1325
+ // to build the Gen[M[K,V]] with both keyGen and valueGen already resolved.
1326
+ D.instance(key.metadata).zip(D.instance(value.metadata)).map { case (keyGen, valueGen) =>
1327
+ new Gen[M[K, V]] {
1328
+ def generate(random: Random): M[K, V] = {
1329
+ val size = random.nextInt(6) // 0 to 5 entries
1330
+
1331
+ if (size == 0) {
1332
+ constructor.emptyObject[K, V]
1333
+ } else {
1334
+ // Build the map by generating each key-value pair and adding it to the builder
1335
+ val builder = constructor.newObjectBuilder[K, V](size)
1336
+ (0 until size).foreach { _ =>
1337
+ constructor.addObject(builder, keyGen.generate(random), valueGen.generate(random))
1338
+ }
1339
+ constructor.resultObject(builder)
1338
1340
  }
1339
- constructor.resultObject(builder)
1340
1341
  }
1341
1342
  }
1342
1343
  }
1343
1344
  }
1344
1345
  ```
1345
1346
 
1346
- The derivation process for maps is similar to sequences, but it requires handling the generation of random values for both keys and values.
1347
+ The derivation process for maps is similar to sequences but requires composing two child instances. We use `.zip` to combine the `Lazy[Gen[K]]` and `Lazy[Gen[V]]` into a single `Lazy`, then `.map` to build the `Gen[M[K,V]]` with both `keyGen` and `valueGen` already resolved. At runtime, a random size (0–5) determines whether we return an empty map or build one entry by entry using `constructor.addObject(builder, keyGen.generate(random), valueGen.generate(random))`.
1347
1348
 
1348
1349
  ### Dynamic Derivation
1349
1350
 
@@ -1419,18 +1420,22 @@ def deriveWrapper[F[_, _], A, B](
1419
1420
  modifiers: Seq[Modifier.Reflect],
1420
1421
  defaultValue: Option[A],
1421
1422
  examples: Seq[A]
1422
- )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[A]] = Lazy {
1423
- val wrappedGen = D.instance(wrapped.metadata)
1423
+ )(implicit F: HasBinding[F], D: DeriveGen.HasInstance[F]): Lazy[Gen[A]] = {
1424
+ // Cast binding to Binding.Wrapper to access the wrap function
1424
1425
  val wrapperBinding = binding.asInstanceOf[Binding.Wrapper[A, B]]
1425
-
1426
- new Gen[A] {
1427
- def generate(random: Random): A =
1428
- wrapperBinding.wrap(wrappedGen.force.generate(random))
1426
+ // Wrappers are non-recursive: use .map so wrappedGen is already resolved
1427
+ // when generate() is called — no .force needed.
1428
+ D.instance(wrapped.metadata).map { wrappedGen =>
1429
+ new Gen[A] {
1430
+ def generate(random: Random): A =
1431
+ // Generate a value of the underlying type B and wrap it into A
1432
+ wrapperBinding.wrap(wrappedGen.generate(random))
1433
+ }
1429
1434
  }
1430
1435
  }
1431
1436
  ```
1432
1437
 
1433
- First, we retrieve the `Gen` instance for the wrapped (underlying) type `B`. Then, within the `generate` method, we generate a random value of type `B` and wrap it into type `A` using the `wrap` function provided by the binding.
1438
+ Wrappers are non-recursive, so we use `.map` composition: `D.instance(wrapped.metadata).map { wrappedGen => ... }` returns a `Lazy[Gen[A]]` that, when forced, builds a `Gen[A]` with `wrappedGen` already resolved. Within `generate()`, we generate a random value of the underlying type `B` and wrap it into `A` using the `wrap` function from the binding — no explicit `.force` is needed.
1434
1439
 
1435
1440
  ### Example Usages
1436
1441
 
@@ -1451,7 +1456,7 @@ Now we can use the derived `Gen[Person]` instance to generate random `Person` va
1451
1456
 
1452
1457
  ```scala
1453
1458
  val random = new Random(42) // Seeded for reproducible output
1454
- // random: Random = scala.util.Random@559ce90
1459
+ // random: Random = scala.util.Random@5cfafbe0
1455
1460
 
1456
1461
  Person.gen.generate(random)
1457
1462
  // res14: Person = Person(name = "p", age = -1360544799)