scalaadvanced

Type Class Derivation with Mirrors

Derive type class instances automatically using Scala 3 Mirrors and inline metaprogramming.

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

// Type class to derive
trait Show[T]:
  def show(value: T): String

object Show:
  given Show[Int] with
    def show(value: Int): String = value.toString

  given Show[String] with
    def show(value: String): String = s"\"$value\""

  given Show[Boolean] with
    def show(value: Boolean): String = value.toString

  given Show[Double] with
    def show(value: Double): String = f"$value%.2f"

  // Show for List
  given [T](using s: Show[T]): Show[List[T]] with
    def show(value: List[T]): String =
      value.map(s.show).mkString("[", ", ", "]")

  // Show for Option
  given [T](using s: Show[T]): Show[Option[T]] with
    def show(value: Option[T]): String = value match
      case Some(v) => s"Some(${s.show(v)})"
      case None    => "None"

  // Derive for products (case classes)
  inline def showProduct[T](value: T, labels: List[String])(
    using m: Mirror.ProductOf[T]
  ): String =
    val elems = value.asInstanceOf[Product].productIterator.toList
    val pairs = labels.zip(elems).map { (label, elem) =>
      s"$label = $elem"
    }
    s"${value.getClass.getSimpleName}(${pairs.mkString(", ")})"

  // For simple demo, manual derivation
  inline given derived[T](using m: Mirror.Of[T]): Show[T] =
    new Show[T]:
      def show(value: T): String =
        value.toString  // simplified; full impl uses compiletime

// Manual instances for our types
case class Point(x: Double, y: Double) derives Show
case class Color(r: Int, g: Int, b: Int) derives Show
case class Pixel(point: Point, color: Color) derives Show

// Custom Show with nice formatting
given Show[Point] with
  def show(value: Point): String = f"(${value.x}%.1f, ${value.y}%.1f)"

given Show[Color] with
  def show(value: Color): String =
    f"rgb(${value.r}, ${value.g}, ${value.b})"

given Show[Pixel] with
  def show(value: Pixel): String =
    s"Pixel at ${summon[Show[Point]].show(value.point)} color=${summon[Show[Color]].show(value.color)}"

def printShow[T: Show](value: T): Unit =
  println(summon[Show[T]].show(value))

@main def run(): Unit =
  printShow(42)
  printShow("hello")
  printShow(true)
  printShow(3.14159)
  printShow(List(1, 2, 3))
  printShow(Option("test"))
  printShow(Option.empty[String])

  val pixel = Pixel(Point(10.5, 20.3), Color(255, 128, 0))
  printShow(pixel)

  val points = List(Point(1, 2), Point(3, 4), Point(5, 6))
  printShow(points)

Use Cases

  • Automatic codec generation
  • Reducing boilerplate with derivation
  • Generic programming utilities

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.