Skip to content

Conversation

@samuel-asleep
Copy link

This PR adds a new documentation section explaining how users can derive type classes, with a focus on codecs.

What’s included

  • An introduction to type-class derivation and why it matters.
  • Practical examples showing how to derive codecs for user-defined types.
  • Coverage of common derivation approaches (including Scala 2 and Scala 3 where applicable).
  • Simple, copy-pasteable examples aimed at new users.

Why this change

Deriving type classes such as codecs is a common task, but the process is not currently documented in a single, approachable place. This addition improves discoverability and onboarding for users who are new to the library or to type-class derivation.

The goal is to provide a concise starting point rather than an exhaustive reference.

No code behavior is changed; this PR is documentation-only.

Fixes #836

Introduce a new documentation section that explains how users can derive
type classes.

Includes practical examples and brief explanations to help new users
understand common derivation patterns and get started quickly.
Copilot AI review requested due to automatic review settings January 26, 2026 04:44
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds dedicated documentation for type-class derivation in ZIO Blocks and wires it into the existing docs, with an emphasis on deriving codecs and configuring derivers.

Changes:

  • Introduces a new docs/reference/derivation.md guide covering basic derivation, configuration, fluent API, custom derivers, and supported formats.
  • Links the new derivation guide from the Schema reference and from the main docs index under “Automatic Type-Class Derivation”.
  • Updates the feature list on the docs homepage to explicitly reference type-class derivation and the new guide.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.

File Description
docs/reference/schema.md Adds a short pointer from the Schema reference to the new Type-Class Derivation guide, improving discoverability of derivation-related docs.
docs/reference/derivation.md New, comprehensive derivation guide with examples for deriving codecs, configuring derivers, using the fluent API, writing custom Deriver instances, and an overview of supported formats; several example snippets currently diverge from the actual APIs.
docs/index.md Updates the feature bullet to highlight “Automatic Type-Class Derivation” and links directly to the new derivation guide from the homepage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 456 to 460
val deconstructor = binding.deconstructor
val fieldValues = fields.map { field =>
val fieldValue = deconstructor.projectDynamic(value, field.label)
val printer = D.instance(field.reflect).value
s"${field.label} = ${printer.print(fieldValue.asInstanceOf[field.A])}"
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this PrettyPrinter example, binding.deconstructor.projectDynamic is used to access record fields, but Binding.Record’s deconstructor is a Deconstructor[A] that does not define projectDynamic, and there is no projectDynamic method anywhere in the codebase. As written, this snippet will not compile under mdoc. Please update the example to use the actual binding/reflect APIs for accessing field values (e.g., via the Term/Reflect abstractions) so that the code matches the real API surface.

Copilot uses AI. Check for mistakes.
Comment on lines 476 to 480
val deconstructor = binding.deconstructor
val caseIndex = deconstructor.indexOf(value)
val selectedCase = cases(caseIndex)
val caseValue = deconstructor.projectDynamic(value, caseIndex)
val printer = D.instance(selectedCase.reflect).value
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variant derivation in this PrettyPrinter example calls binding.deconstructor.indexOf(value) and binding.deconstructor.projectDynamic(value, caseIndex), but Binding.Variant does not have a deconstructor member and there are no indexOf/projectDynamic methods exposed on any binding deconstructor types. This code cannot compile against the current Binding.Variant API and will fail mdoc compilation; consider rewriting the example to use the existing Discriminator/Matchers machinery (or higher-level Reflect APIs) to select the active case and its value.

Suggested change
val deconstructor = binding.deconstructor
val caseIndex = deconstructor.indexOf(value)
val selectedCase = cases(caseIndex)
val caseValue = deconstructor.projectDynamic(value, caseIndex)
val printer = D.instance(selectedCase.reflect).value
val discriminator = binding.discriminator
val matchers = binding.matchers
val caseIndex = discriminator.indexOf(value)
val selectedCase = cases(caseIndex)
val caseValue = matchers.project(value, caseIndex)
val printer = D.instance(selectedCase.reflect).value

Copilot uses AI. Check for mistakes.
Comment on lines 495 to 496
val printer = D.instance(element).value
val elements = binding.deconstructor.toChunk(value)
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For sequences, this example relies on binding.deconstructor.toChunk(value), but Binding.Seq’s deconstructor is a SeqDeconstructor[C] which exposes deconstruct/size (and, for specialized variants, element accessors) rather than a toChunk method; there is no toChunk on SeqDeconstructor in the codebase. This will not compile as-is; please adjust the example to use the real SeqDeconstructor API (e.g., iterating via deconstruct and materializing a collection) so it reflects how custom derivers should be implemented.

Suggested change
val printer = D.instance(element).value
val elements = binding.deconstructor.toChunk(value)
val printer = D.instance(element).value
val deconstructor = binding.deconstructor
val buffer = scala.collection.mutable.ArrayBuffer.empty[A]
deconstructor.deconstruct(value, (a: A) => buffer += a)
val elements = buffer.toVector

Copilot uses AI. Check for mistakes.
Comment on lines 514 to 515
val entries = binding.deconstructor.toChunk(value)
val entryStrings = entries.map { case (k, v) =>
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the map case, this example calls binding.deconstructor.toChunk(value), but Binding.Map’s deconstructor is a MapDeconstructor[M] that exposes deconstruct/size/get* helpers and no toChunk method. This mismatch means the snippet will not compile and misleads users about the available API; consider rewriting it to iterate using MapDeconstructor.deconstruct (or its getKey/getValue helpers) instead.

Suggested change
val entries = binding.deconstructor.toChunk(value)
val entryStrings = entries.map { case (k, v) =>
val deconstructed = binding.deconstructor.deconstruct(value)
val size = binding.deconstructor.size(deconstructed)
val entryStrings = (0 until size).map { i =>
val k = binding.deconstructor.getKey(deconstructed, i)
val v = binding.deconstructor.getValue(deconstructed, i)

Copilot uses AI. Check for mistakes.
)(implicit F: HasBinding[F], D: HasInstance[F]): Lazy[PrettyPrinter[A]] = Lazy {
new PrettyPrinter[A] {
def print(value: A): String = {
val unwrapped = binding.deconstructor.unwrap(value)
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the wrapper case, the example uses binding.deconstructor.unwrap(value), but Binding.Wrapper defines wrap/unwrap methods directly and does not have a deconstructor member. As a result this code does not correspond to any real API and will fail to compile; please update it to call binding.unwrap(value) (or an equivalent pattern) so the sample reflects the actual wrapper binding interface.

Suggested change
val unwrapped = binding.deconstructor.unwrap(value)
val unwrapped = binding.unwrap(value)

Copilot uses AI. Check for mistakes.
Comment on lines 571 to 574
type TypeClass[A] <: Codec[A]
type EncodeOutput
type DecodeInput

Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Format trait shown here does not match the actual implementation in schema/shared/src/main/scala/zio/blocks/schema/codec/Format.scala: in the real code, TypeClass[A] is a subtype of Codec[DecodeInput, EncodeOutput, A], not just Codec[A], and DecodeInput/EncodeOutput are the type parameters of that codec. Please update this snippet so the type members and TypeClass alias line up with the real Format definition, otherwise users attempting to implement custom formats from this example will hit type errors.

Suggested change
type TypeClass[A] <: Codec[A]
type EncodeOutput
type DecodeInput
type DecodeInput
type EncodeOutput
type TypeClass[A] <: Codec[DecodeInput, EncodeOutput, A]

Copilot uses AI. Check for mistakes.

// These calls use cached codec instances
val buffer = ByteBuffer.allocate(1024)
Schema[Person].encode(JsonFormat)(buffer)(person)
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the caching example, the same ByteBuffer is passed to Schema[Person].decode(JsonFormat)(buffer) immediately after encoding without flipping or rewinding it, unlike the earlier Schema reference docs which call buffer.flip() before decode. Given how BinaryCodec implementations work with ByteBuffer, this pattern is unlikely to decode correctly and can confuse users; consider adding a flip/rewind (or using a fresh buffer/view) here so the sample is consistent with the recommended usage elsewhere.

Suggested change
Schema[Person].encode(JsonFormat)(buffer)(person)
Schema[Person].encode(JsonFormat)(buffer)(person)
buffer.flip()

Copilot uses AI. Check for mistakes.
Comment on lines 693 to 695
| **BSON** | `zio-blocks-schema-bson` | MongoDB's binary JSON format (planned) |
| **Protobuf** | `zio-blocks-schema-protobuf` | Protocol Buffers (planned) |
| **Thrift** | `zio-blocks-schema-thrift` | Apache Thrift (planned) |
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Supported Formats table, BSON and Thrift are marked as “planned” ("MongoDB's binary JSON format (planned)" and "Apache Thrift (planned)"), but the repository already contains schema-bson and schema-thrift modules with working implementations and READMEs. This description is now outdated and could mislead users; please adjust these rows to reflect their current availability (while leaving Protobuf as planned, since there is no corresponding module yet).

Suggested change
| **BSON** | `zio-blocks-schema-bson` | MongoDB's binary JSON format (planned) |
| **Protobuf** | `zio-blocks-schema-protobuf` | Protocol Buffers (planned) |
| **Thrift** | `zio-blocks-schema-thrift` | Apache Thrift (planned) |
| **BSON** | `zio-blocks-schema-bson` | MongoDB's binary JSON format |
| **Protobuf** | `zio-blocks-schema-protobuf` | Protocol Buffers (planned) |
| **Thrift** | `zio-blocks-schema-thrift` | Apache Thrift serialization |

Copilot uses AI. Check for mistakes.
Updated the TOON codec description for clarity and corrected caching behavior explanation for the Schema class.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Documentation of Type-class Derivation

1 participant