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 |
|---|---|
|
The type described by the mirror. |
|
The same type without type arguments, for a generic class. |
|
The type name as a literal string type. |
|
A tuple type of the element types: the fields of a product, the cases of a sum. |
|
A tuple type of literal strings with the field names or the case names. |
|
A mirror of a product type (case class, case object) with a method that builds an instance from a |
|
A mirror of a sum type (sealed trait, enum) with a method returning the index of the case of a given value. |
|
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.
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] matchbranches on a type at compile time.erasedValue[T]is a placeholder value of typeTthat exists only for the type checker; it must never be evaluated, and it works only inside aninlinematch. Each step peels one element off the tuple type:EmptyTupleends the recursion,t *: tsbinds the headtand the tailtsas type variables. -
summonInline[Show[t]]looks up an instance of the element type after inlining, so the lookup happens where thederivesclause is, not wheresummonAllwas defined. This is what lets a type class author write generic code without knowing the element types. A plainsummonwould fail to compile here, becausetis not known whensummonAllitself is type-checked. -
constValue[l]reads the literal string out of a label type, andconstValue[m.MirroredLabel]does the same for the type name. -
inline m matchon the mirror is a compile-time match on the kind of mirror: only the matching branch remains in the generated code, so a product getsshowProductand an enum getsshowSum. -
showProductandshowSumare ordinary methods. They take the already-summoned instances as a by-nameList[Show[?]]and cast the elements withasInstanceOf, because the types of the elements are erased after the compile-time work is done.showSumcallss.ordinal(a)to pick the right instance. -
The element instances are held in a
lazy valinderivedand passed by name. That is what makes recursive types work:Routehas a field of typeRoute, so buildingShow[Route]needsShow[Route], and the lazy value lets the inner reference resolve to the instance being built. The output line startingLeg(shows a three-levelRouteprinted correctly. See Limits and pitfalls for what happens without the laziness. -
Pair[A]is a generic case class, andderives Showworks for it too. For a type with type parameters the compiler adds a given with a context bound per parameter, heregiven [A: Show] => Show[Pair[A]] = Show.derived, so the instance exists for everyAthat has aShow. The companion above writes that given by hand only to show what the clause expands to: withcase class Pair[A](first: A, second: A) derives Showand no companion, the program printed the same six lines on 3.10.0. -
The element instances of
SegmentareShow[Flat]andShow[Climb]. They are not declared withderives; the compiler finds them throughShow.deriveditself, because a case of an enum is a product with its own mirror andderivedis aninline giventhat 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 derivedhands its work toderivedImplwith a splice,${ derivedImpl[T]('m) }. The macro implementation receives the mirror as a quoted expression,Expr[Mirror.ProductOf[T]], and the type as aType[T]. -
TypeRepr.of[T].typeSymbol.caseFieldslists the symbols of the case class parameters. Using the compiler’squotes.reflectAPI instead ofMirrorlets the macro build a field access for each field. -
For each field, the macro matches its type as
'[t]and asksExpr.summon[Eq[t]]for the instance at macro expansion time. When there is none,report.errorAndAbortgives a custom, field-specific message. Adding aBooleanfield toTrailproduces:[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 generatedeqvhas 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
Showabove keeps the element instances in alazy valand passes them by name. For a recursive enum such asenum Route: case Stop(name: String); case Leg(from: Stop, rest: Route)the instance forRouteneeds the instance forRoute. A first version ofderivedthat summoned the element instances eagerly, without thelazy valand 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 alazy valand passes them by name for this reason. -
Errors point at the
derivesclause. The caret is on the clause, not on the missing instance field, unless the macro reports one. Read theI 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 ShowcallsShow.derived.
Comparison with other languages
-
Java has no derivation. Records get
equals,hashCodeandtoStringfrom 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.serializationgenerates a serializer for a class annotated@Serializable. The plugin is fixed per library, whereasderivesis 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
derivingfor a closed set of classes andGenericfor the open case, with an analogous idea toMirror.
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.GenericandLabelledGenericfor generic programming. For new Scala 3 code, useMirror. -
implicit def derived[T]: TC[T] = macro ...andscala.reflectmacros. 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])withMirror.ProductandMirror.Sumas the pattern, then match on the mirror withinline m match. That is still the pattern; only the helper names andsummonAll-style recursion differ from library to library.