scalaadvanced

Automatic Given Instance Derivation

Derive given instances for product and sum types using Scala 3 derives keyword and Mirror.

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

// Type class
trait Describe[T]:
  def describe(value: T): String

object Describe:
  // Primitives
  given Describe[Int] with
    def describe(value: Int): String = s"Int($value)"

  given Describe[String] with
    def describe(value: String): String = s"String(\"$value\")"

  given Describe[Boolean] with
    def describe(value: Boolean): String = s"Boolean($value)"

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

  // For Option
  given [T: Describe]: Describe[Option[T]] with
    def describe(value: Option[T]): String = value match
      case Some(v) => s"Some(${summon[Describe[T]].describe(v)})"
      case None => "None"

  // For List
  given [T: Describe]: Describe[List[T]] with
    def describe(value: List[T]): String =
      val d = summon[Describe[T]]
      value.map(d.describe).mkString("List(", ", ", ")")

  // Helper for product types
  inline def describeProduct[T <: Tuple](values: T, index: Int = 0): List[String] =
    inline values match
      case _: EmptyTuple => Nil
      case v: (h *: t) =>
        val desc = summonInline[Describe[h]].describe(v.head)
        desc :: describeProduct[t](v.tail, index + 1)

  // Derive for any product type (case class)
  inline given derived[T](using m: Mirror.ProductOf[T]): Describe[T] =
    new Describe[T]:
      def describe(value: T): String =
        val name = value.getClass.getSimpleName
        val elems = value.asInstanceOf[Product].productIterator.toList
        val fields = value.asInstanceOf[Product].productElementNames.toList
        val pairs = fields.zip(elems).map { (f, v) =>
          s"$f=$v"
        }
        s"$name(${pairs.mkString(", ")})"

// Usage: derives keyword
case class Point(x: Double, y: Double) derives Describe
case class Color(r: Int, g: Int, b: Int) derives Describe
case class Pixel(position: Point, color: Color, label: String) derives Describe

// Using it manually
case class Config(host: String, port: Int, debug: Boolean)
given Describe[Config] = Describe.derived

// Extension for ergonomic usage
extension [T: Describe](value: T)
  def describe: String = summon[Describe[T]].describe(value)

@main def run(): Unit =
  // Primitives
  println(42.describe)
  println("hello".describe)
  println(true.describe)
  println(3.14.describe)

  // Derived instances
  val point = Point(1.5, 2.5)
  println(point.describe)

  val color = Color(255, 128, 0)
  println(color.describe)

  val pixel = Pixel(Point(10, 20), Color(0, 255, 0), "green")
  println(pixel.describe)

  val config = Config("localhost", 8080, true)
  println(config.describe)

  // Collections
  println(List(1, 2, 3).describe)
  println(Option("test").describe)
  println(Option.empty[Int].describe)

Use Cases

  • Automatic serialization/debugging
  • Reducing type class boilerplate
  • Generic programming with derives

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.