Created
June 5, 2026 20:33
-
-
Save bthuillier/4f65135b17bd41f41adcb862ef6fd223 to your computer and use it in GitHub Desktop.
Full Working Example For article
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //> using scala 3.8.3 | |
| //> using options -feature -language:strictEquality -Wunused:all -Wsafe-init -deprecation | |
| import java.util.UUID | |
| import scala.deriving.Mirror | |
| import scala.compiletime.* | |
| import scala.language.implicitConversions | |
| trait Debuggable[T] { | |
| extension (x: T) def debug: String | |
| } | |
| object Debuggable { | |
| given Debuggable[Byte] with | |
| extension (x: Byte) def debug: String = x.toString | |
| given Debuggable[Short] with | |
| extension (x: Short) def debug: String = x.toString | |
| given Debuggable[Int] with | |
| extension (x: Int) def debug: String = x.toString | |
| given Debuggable[Long] with | |
| extension (x: Long) def debug: String = x.toString | |
| given Debuggable[Float] with | |
| extension (x: Float) def debug: String = x.toString | |
| given Debuggable[Double] with | |
| extension (x: Double) def debug: String = x.toString | |
| given Debuggable[BigInt] with | |
| extension (x: BigInt) def debug: String = x.toString | |
| given Debuggable[BigDecimal] with | |
| extension (x: BigDecimal) def debug: String = x.toString | |
| given Debuggable[String] with | |
| extension (x: String) def debug: String = x | |
| given Debuggable[UUID] with | |
| extension (x: UUID) def debug: String = x.toString | |
| given [T](using d: Debuggable[T]): Debuggable[List[T]] with | |
| extension (x: List[T]) | |
| def debug: String = x.map(_.debug).mkString("[", ", ", "]") | |
| given [K, V](using dk: Debuggable[K], dv: Debuggable[V]): Debuggable[ | |
| Map[K, V] | |
| ] with | |
| extension (x: Map[K, V]) | |
| def debug: String = | |
| x.map((k, v) => s"${k.debug}=${v.debug}").mkString("[", ", ", "]") | |
| given [T](using d: Debuggable[T]): Debuggable[Option[T]] with | |
| extension (x: Option[T]) | |
| def debug: String = x match | |
| case Some(v) => s"${v.debug}" | |
| case None => "none" | |
| inline def summonAll[T <: Tuple]: List[Debuggable[?]] = | |
| inline erasedValue[T] match | |
| case _: EmptyTuple => Nil | |
| case _: (head *: tail) => | |
| deriveOrSummon[head] :: summonAll[tail] | |
| inline def deriveOrSummon[T]: Debuggable[T] = | |
| scala.compiletime.summonFrom { | |
| case d: Debuggable[T] => d | |
| case m: Mirror.Of[T] => derived[T](using m) | |
| } | |
| inline def labelsOf[T <: Tuple]: List[String] = | |
| inline erasedValue[T] match | |
| case _: EmptyTuple => Nil | |
| case _: (head *: tail) => | |
| constValue[head].asInstanceOf[String] :: labelsOf[tail] | |
| inline def derived[T](using m: Mirror.Of[T]): Debuggable[T] = | |
| val instances = summonAll[m.MirroredElemTypes] | |
| val labels = labelsOf[m.MirroredElemLabels] | |
| val typeName = constValue[m.MirroredLabel] | |
| inline m match | |
| case _: Mirror.ProductOf[T] => | |
| productDebuggable(typeName, labels, instances) | |
| case s: Mirror.SumOf[T] => sumDebuggable(instances, s) | |
| private def toSnakeCase(name: String): String = | |
| s" $name " | |
| .sliding(3) | |
| .flatMap { window => | |
| val prev = window.charAt(0) | |
| val c = window.charAt(1) | |
| val next = window.charAt(2) | |
| val boundary = | |
| c.isUpper && (prev.isLower || prev.isDigit || (prev.isUpper && next.isLower)) | |
| (if boundary then "_" else "") + c.toLower | |
| } | |
| .mkString | |
| private def productDebuggable[T]( | |
| typeName: String, | |
| labels: List[String], | |
| instances: List[Debuggable[?]] | |
| ): Debuggable[T] = new Debuggable[T] { | |
| extension (x: T) | |
| def debug: String = | |
| val fields = x.asInstanceOf[Product].productIterator.toList | |
| val parts = fields.lazyZip(instances).lazyZip(labels).map { | |
| (value, inst, label) => | |
| s"$label=${inst.asInstanceOf[Debuggable[Any]].debug(value)}" | |
| } | |
| parts.mkString(s"${toSnakeCase(typeName)}{", ", ", "}") | |
| } | |
| private def sumDebuggable[T]( | |
| instances: List[Debuggable[?]], | |
| s: Mirror.SumOf[T] | |
| ): Debuggable[T] = new Debuggable[T] { | |
| extension (x: T) | |
| def debug: String = | |
| val ord = s.ordinal(x) | |
| instances(ord).asInstanceOf[Debuggable[Any]].debug(x) | |
| } | |
| } | |
| object literal { | |
| class DebugArg(val rendered: String) | |
| given convert[T](using d: Debuggable[T]): Conversion[T, DebugArg] with | |
| def apply(x: T): DebugArg = DebugArg(x.debug) | |
| extension (sc: StringContext) | |
| def debug(args: DebugArg*): String = | |
| sc.s(args.map(_.rendered)*) | |
| } | |
| case class Person(name: String, age: Int) derives Debuggable | |
| enum Shape derives Debuggable: | |
| case Circle(radius: Double) | |
| case Rectangle(width: Double, height: Double) | |
| case Dot | |
| case class TestValue(x: String) derives Debuggable | |
| case class Test(name: String, value: TestValue) derives Debuggable | |
| @main def run() = { | |
| import Debuggable.given | |
| import literal.* | |
| println((42: Byte).debug) | |
| println((42: Short).debug) | |
| println(42.debug) | |
| println(42L.debug) | |
| println(42.0f.debug) | |
| println(42.0.debug) | |
| println(BigInt(42).debug) | |
| println(BigDecimal(42).debug) | |
| println("hello".debug) | |
| println(UUID.randomUUID().debug) | |
| println(List(1, 2, 3).debug) | |
| println(Map("a" -> 1, "b" -> 2).debug) | |
| println(Option(42).debug) | |
| println((None: Option[Int]).debug) | |
| println(Person("Alice", 30).debug) | |
| println((Shape.Circle(1.5): Shape).debug) | |
| println((Shape.Rectangle(2.0, 3.0): Shape).debug) | |
| println((Shape.Dot: Shape).debug) | |
| println(Test("test", TestValue("coucou")).debug) | |
| val p = Person("Alice", 30) | |
| val s: Shape = Shape.Circle(1.5) | |
| println(debug"person = $p, shape = $s, count = ${42}") | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment