Skip to content

Instantly share code, notes, and snippets.

@bthuillier
Created June 5, 2026 20:33
Show Gist options
  • Select an option

  • Save bthuillier/4f65135b17bd41f41adcb862ef6fd223 to your computer and use it in GitHub Desktop.

Select an option

Save bthuillier/4f65135b17bd41f41adcb862ef6fd223 to your computer and use it in GitHub Desktop.
Full Working Example For article
//> 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