Type Class Derivation

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Scala 3.10 (3.10.0, released 2026-10-12) and every example was compiled and run with Scala 3.10.0 through Scala CLI on JDK 26 (checked on 2026-10-15). The derives clause, Mirror, inline and scala.quoted macros are all part of Scala 3 since 3.0, and no feature on this page is experimental or in preview, so the mechanism also applies to the 3.3 LTS and 3.9 LTS lines. The code, however, uses the given syntax of Scala 3.6 and later: on 3.3 write given Show[Int] with instead of given Show[Int]:, and given [A: Show]: Show[Pair[A]] instead of given [A: Show] => Show[Pair[A]]. With those two changes the program in Writing derived by hand printed the same output on 3.3.8. The other examples were run on 3.10.0 only.

Writing a type class instance for each data type by hand is the part of the pattern on Type Classes that does not scale. A Show[Trail] is mechanical: show the type name, then each field name and value. Derivation lets the compiler build that instance from the structure of the type. A type class author writes a single derived method that works for any case class or enum, and a user then adds derives Show to a definition. This is the Scala 3 replacement for the macro and shapeless based derivation of Scala 2 libraries. The examples use the trailhead scenario, a toolkit that plans hikes on a network of mountain trails.

The derives clause

A derives clause on a class, trait, object or enum lists the type classes to derive. For each name TC in the clause the compiler generates a given in the companion object, defined to call TC.derived. The first definition below is shorthand for the second:

case class Trail(name: String, lengthKm: Double, ascentM: Int) derives Show

// is shorthand for

case class Trail(name: String, lengthKm: Double, ascentM: Int)
object Trail:
  given derived$Show: Show[Trail] = Show.derived

The generated given is named derived$Show, and it is reachable by that name: Trail.derived$Show.show(...) printed the same text as trail.show in the program below. Because the given lives in the companion of Trail, it is in the implicit scope of Show[Trail] and is found without any import, which is the reason derives is better than a handwritten given in a separate object. A derives clause on an enum defines the given in the enum companion; the type class can be a user-defined trait or a library trait such as CanEqual.

For derives TC to work, TC needs a method derived in its companion with a signature the compiler can call. For a type class TC[A] the usual form takes a Mirror as a given parameter:

inline def derived[T](using m: Mirror.Of[T]): TC[T]

The next section explains the Mirror. The full rules for the shape of derived, including type classes over a type constructor F[_], are in the reference page on derivation (see References).

The Mirror

The compiler synthesizes a scala.deriving.Mirror for every case class, case object, enum, enum case and sealed hierarchy of such types. A Mirror is a value that describes the shape of a type with type members, so code can inspect a type through the type system and then rebuild instances of it. Summoning Mirror.Of[T] never needs a handwritten given.

Member Meaning

MirroredType

The type described by the mirror.

MirroredMonoType

The same type without type arguments, for a generic class.

MirroredLabel

The type name as a literal string type.

MirroredElemTypes

A tuple type of the element types: the fields of a product, the cases of a sum.

MirroredElemLabels

A tuple type of literal strings with the field names or the case names.

Mirror.Product and fromProduct

A mirror of a product type (case class, case object) with a method that builds an instance from a Product.

Mirror.Sum and ordinal

A mirror of a sum type (sealed trait, enum) with a method returning the index of the case of a given value.

Mirror.Singleton

A mirror of a case object or a parameterless enum case; its element tuples are empty.

Mirror.ProductOf[T] and Mirror.SumOf[T] are the usual type aliases for a mirror of a product or sum of T. Mirror.Of[T] is either, depending on T. The program below prints the labels at compile time, rebuilds a case class from a tuple, and asks a sum for the index of a case.

//> using scala 3.10.0

package trailhead

import scala.deriving.Mirror

case class Trail(name: String, lengthKm: Double, ascentM: Int)

enum Segment:
  case Flat(km: Double)
  case Climb(km: Double, ascentM: Int)
  case Rest

@main def run(): Unit =
  val m = summon[Mirror.Of[Trail]]
  println(scala.compiletime.constValue[m.MirroredLabel])
  inline def labels[T](using m: Mirror.Of[T]) =
    scala.compiletime.constValueTuple[m.MirroredElemLabels]
  println(labels[Trail])
  println(labels[Segment])
  val trail = Trail("Ridge", 8.5, 900)
  val product = summon[Mirror.ProductOf[Trail]]
  println(Tuple.fromProductTyped(trail))
  println(product.fromProduct(("Lake", 3.0, 120)))
  val sum = summon[Mirror.SumOf[Segment]]
  println(sum.ordinal(Segment.Climb(2.0, 300)))
  println(sum.ordinal(Segment.Rest))
  println(summon[Mirror.Of[Segment.Rest.type]].isInstanceOf[Mirror.Singleton])
Trail
(name,lengthKm,ascentM)
(Flat,Climb,Rest)
(Ridge,8.5,900)
Trail(Lake,3.0,120)
1
2
true

The labels are computed from types, not from reflection. constValueTuple[m.MirroredElemLabels] expands, at compile time, into the literal tuple ("name", "lengthKm", "ascentM"), so there is no reflective lookup of field names at runtime. The mirror for a sum is built from the cases: its MirroredElemLabels are the case names, and the ordinal of an enum value is its position in that tuple. A sum of cases that are themselves products, like Climb, also has a mirror of its own, which the next section uses.

How the compiler resolves derives

The diagram follows the path of Trail("Ridge", 8.5, 900).show after case class Trail(...) derives Show.

flowchart TD A["case class Trail ... derives Show"] --> B["compiler adds to object Trail:
given derived$Show: Show[Trail] = Show.derived"] B --> C["inline Show.derived[Trail]
needs Mirror.Of[Trail]"] C --> D["compiler synthesizes the Mirror
labels: name, lengthKm, ascentM
types: String, Double, Int"] D --> E{"Mirror kind?"} E -- "ProductOf" --> F["summonInline Show for each element type
Show[String], Show[Double], Show[Int]"] E -- "SumOf" --> G["summonInline Show for each case
one instance per subclass"] F --> H["showProduct builds the Show[Trail] instance"] G --> I["showSum picks the instance with ordinal"] H --> J["trail.show finds Show[Trail] in the companion"] I --> J

Every step before J happens at compile time. If any element type has no Show, the failure is a compile error at the derives clause (see Error messages), never a ClassCastException at runtime.

Writing derived by hand

The standard recipe has four parts: summon one instance per element, build a product instance, build a sum instance, and dispatch on the kind of mirror in an inline method. The complete program below defines a Show type class whose derived method works for any case class or enum made of Int, Double, String and nested derivable types.

//> using scala 3.10.0

package trailhead

import scala.compiletime.{constValue, erasedValue, summonInline}
import scala.deriving.Mirror

trait Show[A]:
  extension (a: A) def show: String

object Show:
  given Show[Int]:
    extension (a: Int) def show: String = a.toString
  given Show[Double]:
    extension (a: Double) def show: String = a.toString
  given Show[String]:
    extension (a: String) def show: String = "\"" + a + "\""

  // one Show instance per element type, in declaration order
  inline def summonAll[T <: Tuple]: List[Show[?]] =
    inline erasedValue[T] match
      case _: EmptyTuple => Nil
      case _: (t *: ts)  => summonInline[Show[t]] :: summonAll[ts]

  inline def labels[T <: Tuple]: List[String] =
    inline erasedValue[T] match
      case _: EmptyTuple => Nil
      case _: (l *: ls)  => constValue[l].asInstanceOf[String] :: labels[ls]

  def showProduct[T](p: Mirror.ProductOf[T], name: String, names: List[String], shows: => List[Show[?]]): Show[T] =
    lazy val elems = shows
    new Show[T]:
      extension (a: T) def show: String =
        val fields = a.asInstanceOf[Product].productIterator.zip(elems).zip(names).map:
          case ((v, s), n) => s"$n = ${s.asInstanceOf[Show[Any]].show(v)}"
        fields.mkString(s"$name(", ", ", ")")

  def showSum[T](s: Mirror.SumOf[T], shows: => List[Show[?]]): Show[T] =
    lazy val elems = shows
    new Show[T]:
      extension (a: T) def show: String = elems(s.ordinal(a)).asInstanceOf[Show[Any]].show(a)

  inline given derived[T](using m: Mirror.Of[T]): Show[T] =
    lazy val shows = summonAll[m.MirroredElemTypes]
    inline m match
      case s: Mirror.SumOf[T]     => showSum(s, shows)
      case p: Mirror.ProductOf[T] => showProduct(p, constValue[m.MirroredLabel], labels[m.MirroredElemLabels], shows)

case class Trail(name: String, lengthKm: Double, ascentM: Int) derives Show

enum Segment derives Show:
  case Flat(km: Double)
  case Climb(km: Double, ascentM: Int)

enum Route derives Show:
  case Stop(name: String)
  case Leg(from: Stop, rest: Route)

case class Pair[A](first: A, second: A)
object Pair:
  given [A: Show] => Show[Pair[A]] = Show.derived

@main def run(): Unit =
  println(Trail("Ridge", 8.5, 900).show)
  println(Segment.Climb(2.0, 300).show)
  println((Segment.Flat(1.5): Segment).show)
  println((Route.Leg(Route.Stop("Hut"), Route.Leg(Route.Stop("Pass"), Route.Stop("Lake"))): Route).show)
  println(Pair(1, 2).show)
  println(summon[Show[Trail]].show(Trail("Lake", 3.0, 120)))
Trail(name = "Ridge", lengthKm = 8.5, ascentM = 900)
Climb(km = 2.0, ascentM = 300)
Flat(km = 1.5)
Leg(from = Stop(name = "Hut"), rest = Leg(from = Stop(name = "Pass"), rest = Stop(name = "Lake")))
Pair(first = 1, second = 2)
Trail(name = "Lake", lengthKm = 3.0, ascentM = 120)

The pieces are:

  • inline erasedValue[T] match branches on a type at compile time. erasedValue[T] is a placeholder value of type T that exists only for the type checker; it must never be evaluated, and it works only inside an inline match. Each step peels one element off the tuple type: EmptyTuple ends the recursion, t *: ts binds the head t and the tail ts as type variables.

  • summonInline[Show[t]] looks up an instance of the element type after inlining, so the lookup happens where the derives clause is, not where summonAll was defined. This is what lets a type class author write generic code without knowing the element types. A plain summon would fail to compile here, because t is not known when summonAll itself is type-checked.

  • constValue[l] reads the literal string out of a label type, and constValue[m.MirroredLabel] does the same for the type name.

  • inline m match on the mirror is a compile-time match on the kind of mirror: only the matching branch remains in the generated code, so a product gets showProduct and an enum gets showSum.

  • showProduct and showSum are ordinary methods. They take the already-summoned instances as a by-name List[Show[?]] and cast the elements with asInstanceOf, because the types of the elements are erased after the compile-time work is done. showSum calls s.ordinal(a) to pick the right instance.

  • The element instances are held in a lazy val in derived and passed by name. That is what makes recursive types work: Route has a field of type Route, so building Show[Route] needs Show[Route], and the lazy value lets the inner reference resolve to the instance being built. The output line starting Leg( shows a three-level Route printed correctly. See Limits and pitfalls for what happens without the laziness.

  • Pair[A] is a generic case class, and derives Show works for it too. For a type with type parameters the compiler adds a given with a context bound per parameter, here given [A: Show] => Show[Pair[A]] = Show.derived, so the instance exists for every A that has a Show. The companion above writes that given by hand only to show what the clause expands to: with case class Pair[A](first: A, second: A) derives Show and no companion, the program printed the same six lines on 3.10.0.

  • The element instances of Segment are Show[Flat] and Show[Climb]. They are not declared with derives; the compiler finds them through Show.derived itself, because a case of an enum is a product with its own mirror and derived is an inline given that accepts any type that has a mirror.

The given Show[Int] family at the top of object Show is the base case: derivation reduces a type to its fields, and a field of a primitive type must have an instance written by hand. Adding given Show[Boolean] extends the set of types that can appear inside a derivable case class.

Error messages

If a field has no instance, the compiler reports exactly which one and why it could not derive it. With a Boolean field and no Show[Boolean]:

[error] ./e1.scala:47:53
[error] No given instance of type trailhead.Show[Boolean] was found.
[error] I found:
[error]
[error]     trailhead.Show.derived[Boolean](
[error]       /* missing */summon[scala.deriving.Mirror.Of[Boolean]])
[error]
[error] But Failed to synthesize an instance of type scala.deriving.Mirror.Of[Boolean]:
[error] 	* class Boolean is not a generic product because it is not a case class
[error] 	* class Boolean is not a generic sum because it is not a sealed class.
[error] case class Hut(name: String, open: Boolean) derives Show
[error]                                                     ^

The caret points at the derives clause, which is where the instance is demanded. The message shows that the compiler first tried Show.derived[Boolean], then found that Boolean is neither a case class nor a sealed type. The fix is a given Show[Boolean].

A class that is not a case class has no mirror at all:

[error] ./e2.scala:47:39
[error] No given instance of type scala.deriving.Mirror.Of[trailhead.Plain] was found for parameter m of given instance derived in object Show. Failed to synthesize an instance of type scala.deriving.Mirror.Of[trailhead.Plain]:
[error] 	* class Plain is not a generic product because it is not a case class
[error] 	* class Plain is not a generic sum because it is not a sealed class
[error] class Plain(val name: String) derives Show
[error]                                       ^

So a regular class, a non-sealed trait or a Java class cannot use derives with a mirror-based type class. The options are to turn it into a case class, to write the instance by hand, or to write the instance in terms of another type that can be derived.

Macro-based derivation

The inline approach is simple but limited to what erasedValue and summonInline can express, and it generates a call to a generic helper (showProduct) that boxes the fields and loops over a list. For code that should be as fast as a handwritten instance, derived can instead be a macro: an inline method that splices a block of generated code written with quotes ('{ ... }) and splices (${ ... }). Here the macro builds x.name == y.name && x.lengthKm == y.lengthKm && ... for an Eq type class, with no list and no casts.

package trailhead

import scala.deriving.Mirror
import scala.quoted.*

trait Eq[T]:
  def eqv(x: T, y: T): Boolean

object Eq:
  given Eq[Int]:
    def eqv(x: Int, y: Int): Boolean = x == y
  given Eq[Double]:
    def eqv(x: Double, y: Double): Boolean = x == y
  given Eq[String]:
    def eqv(x: String, y: String): Boolean = x == y

  // Builds, at compile time, a conjunction x.f1 == y.f1 && x.f2 == y.f2 ...
  inline def derived[T](using m: Mirror.ProductOf[T]): Eq[T] = ${ derivedImpl[T]('m) }

  private def derivedImpl[T: Type](m: Expr[Mirror.ProductOf[T]])(using Quotes): Expr[Eq[T]] =
    import quotes.reflect.*
    val fields = TypeRepr.of[T].typeSymbol.caseFields
    '{
      new Eq[T]:
        def eqv(x: T, y: T): Boolean = ${
          val tests = fields.map: f =>
            f.tree match
              case v: ValDef =>
                v.tpt.tpe.asType match
                  case '[t] =>
                    val ev = Expr.summon[Eq[t]].getOrElse:
                      report.errorAndAbort(s"No Eq[${Type.show[t]}] for field ${f.name}")
                    '{ $ev.eqv(${ Select('{ x }.asTerm, f).asExprOf[t] }, ${ Select('{ y }.asTerm, f).asExprOf[t] }) }
          tests.foldLeft[Expr[Boolean]]('{ true })((acc, t) => '{ $acc && $t })
        }
    }

The macro sits in Eq.scala, and the program that uses it is a second file in the same Scala CLI project:

//> using scala 3.10.0
//> using file Eq.scala
package trailhead

case class Trail(name: String, lengthKm: Double, ascentM: Int) derives Eq

@main def run(): Unit =
  val a = Trail("Ridge", 8.5, 900)
  println(summon[Eq[Trail]].eqv(a, a.copy()))
  println(summon[Eq[Trail]].eqv(a, a.copy(ascentM = 901)))
true
false

The way it works:

  • inline def derived hands its work to derivedImpl with a splice, ${ derivedImpl[T]('m) }. The macro implementation receives the mirror as a quoted expression, Expr[Mirror.ProductOf[T]], and the type as a Type[T].

  • TypeRepr.of[T].typeSymbol.caseFields lists the symbols of the case class parameters. Using the compiler’s quotes.reflect API instead of Mirror lets the macro build a field access for each field.

  • For each field, the macro matches its type as '[t] and asks Expr.summon[Eq[t]] for the instance at macro expansion time. When there is none, report.errorAndAbort gives a custom, field-specific message. Adding a Boolean field to Trail produces:

    [error] ./Main.scala:5:87
    [error] No Eq[scala.Boolean] for field open
    [error] case class Trail(name: String, lengthKm: Double, ascentM: Int, open: Boolean) derives Eq
    [error]                                                                                       ^
  • The fold at the end combines the field comparisons into one && expression, so the generated eqv has no loop.

Macros, quotes, splices and quotes.reflect are covered in Macros: Quotes & Splices and Reflection with quotes.reflect; this page only shows how they plug into derives. The two files compiled together here in a single Scala CLI module. Scala 3 allows an inline macro to be used in the same project once its definition has compiled, but most builds keep macros in a separate module or file for clarity and to keep incremental compilation fast. Prefer the inline approach first and move to a macro only when profiling shows the generic helper is a bottleneck.

Deriving CanEqual

CanEqual is a type class too, and it is the most common use of derives in application code. Under -language:strictEquality the compiler rejects == between types that have no CanEqual; writing derives CanEqual on a case class or enum opts it in for comparison with itself.

//> using scala 3.10.0
//> using option -language:strictEquality

package trailhead

case class Trail(name: String, lengthKm: Double) derives CanEqual
case class Hut(name: String)

enum Segment derives CanEqual:
  case Flat, Climb

@main def run(): Unit =
  println(Trail("Ridge", 8.5) == Trail("Ridge", 8.5))
  println(Segment.Flat == Segment.Climb)
  println(Hut("A") == Hut("A"))

Compiling the program above stops at the comparison of two Hut values, which has no CanEqual instance:

[error] ./canequal.scala:15:11
[error] Values of types trailhead.Hut and trailhead.Hut cannot be compared with == or !=
[error]   println(Hut("A") == Hut("A"))
[error]           ^^^^^^^^^^^^^^^^^^^^

The two comparisons of Trail and Segment compile; removing the Hut line makes the program print true and false. The CanEqual.derived that derives calls takes no mirror: it is a single CanEqual[Any, Any]. See Multiversal Equality for the rules and for how enums, sealed traits and collections get their instances.

Limits and pitfalls

  • Recursive types. The Show above keeps the element instances in a lazy val and passes them by name. For a recursive enum such as enum Route: case Stop(name: String); case Leg(from: Stop, rest: Route) the instance for Route needs the instance for Route. A first version of derived that summoned the element instances eagerly, without the lazy val and by-name parameter used above, did not terminate when applied to such an enum: the compiler was still working after two minutes and was stopped. The Reference version also keeps its element instances in a lazy val and passes them by name for this reason.

  • Errors point at the derives clause. The caret is on the clause, not on the missing instance field, unless the macro reports one. Read the I found: part of the message.

  • Only products and sums. Opaque types, Java classes, type aliases and non-sealed traits have no mirror. Define the instance by hand and reuse it as an element instance.

  • The name derived. The method name is not configurable: derives Show calls Show.derived.

Comparison with other languages

  • Java has no derivation. Records get equals, hashCode and toString from the compiler, but a library type class such as a JSON serializer is driven by reflection at runtime (Jackson) or by an annotation processor (see Java records and sealed classes). A Scala mirror gives the same convenience at compile time, with no reflection.

  • Kotlin uses a compiler plugin for the same job: kotlinx.serialization generates a serializer for a class annotated @Serializable. The plugin is fixed per library, whereas derives is a general language feature that any library can use (see Extension Functions & Scope Functions for the extension side of the story).

  • Rust has #[derive(Debug, Clone, ...)], the closest relative, implemented as procedural macros. Scala’s derivation is written in ordinary Scala for the common case and macros are the optional step.

  • Haskell has deriving for a closed set of classes and Generic for the open case, with an analogous idea to Mirror.

Scala 2 form

Scala 2 had no derives and no Mirror. Generic derivation was done with the shapeless library, which represents a case class as an HList through Generic, or with macro annotations and compiler plugins. Scala 2 libraries typically exposed the result as an explicit call per type, such as a deriveCodec[Trail] helper, where Scala 3 libraries accept a derives clause.

Where the books are outdated

Books written for Scala 2 or for the first Scala 3 releases show the following forms. The current form is the one used in this page.

  • shapeless.Generic and LabelledGeneric for generic programming. For new Scala 3 code, use Mirror.

  • implicit def derived[T]: TC[T] = macro ... and scala.reflect macros. Scala 3 macros use quotes and splices, and the simple cases need no macro at all.

  • Early Scala 3 tutorials that write inline given derived[T](using Mirror.Of[T]) with Mirror.Product and Mirror.Sum as the pattern, then match on the mirror with inline m match. That is still the pattern; only the helper names and summonAll-style recursion differ from library to library.