Skip to content

Instantly share code, notes, and snippets.

@ghostdogpr
Last active February 8, 2025 08:46
Show Gist options
  • Save ghostdogpr/6f2ca0939c67765a0657a255ed653765 to your computer and use it in GitHub Desktop.
Save ghostdogpr/6f2ca0939c67765a0657a255ed653765 to your computer and use it in GitHub Desktop.
Sum Type Derivation
import scala.annotation.nowarn
import scala.deriving.Mirror
trait TC[A] {
def show(a: A): Unit
}
@nowarn
inline def gen[A](using m: Mirror.SumOf[A]): TC[A] = {
val subTypes = compiletime.summonAll[Tuple.Map[m.MirroredElemTypes, TC]]
new TC[A] {
def show(a: A): Unit =
subTypes(m.ordinal(a)).asInstanceOf[TC[A]].show(a)
}
}
sealed trait Parent
case class A() extends Parent
object A {
given TC[A] = new TC[A] {
def show(a: A): Unit = println("a")
}
}
case class B() extends Parent
object B {
given TC[B] = new TC[B] {
def show(a: B): Unit = println("b")
}
}
object C extends Parent {
given TC[C.type] = new TC[C.type] {
def show(a: C.type): Unit = println("c")
}
}
val tc = gen[Parent]
tc.show(A())
tc.show(B())
tc.show(C)
@jivanic-demystdata
Copy link

jivanic-demystdata commented Jan 18, 2025

Instead of:

      subTypes.get(typeName) orElse subTypes.get(typeName.dropRight(1)) match {
        case Some(tc) => tc.show(a)
        case None => throw new Exception(s"There is no TC typeclass instance for $typeName")
      }

you could write:

subTypes
  .getOrElse(typeName.stripSuffix("$"), throw new Exception(s"There is no TC typeclass instance for $typeName"))
  .show(a)

The .stripSuffix("$") is code I've seen in Spark codebase
See https://github.com/search?q=repo%3Aapache%2Fspark%20stripSuffix(%22%24%22)&type=code

Not sure it's any better. Maybe a bit more explicit than .dropRight(1) but it's pretty similar and maybe not worth it.

@MartinHH
Copy link

MartinHH commented Feb 8, 2025

Thanks for pointing me to using summonAll instead of recursive inlining - for example, the following will hit "Maximal number of successive inlines (32) exceeded" with scala 3.3.5, but will compile with 3.4.0 and above:

inline def productInstance[T](using p: scala.deriving.Mirror.ProductOf[T]) =
  scala.compiletime.summonAll[Tuple.Map[p.MirroredElemTypes, Ordering]]

case class BigCaseClass(
  a1: Int, b1: Int, c1: Int, d1: Int, e1: Int, f1: Int, g1: Int, h1: Int, i1: Int, j1: Int,
  k1: Int, l1: Int, m1: Int, n1: Int, o1: Int, p1: Int, q1: Int, r1: Int, s1: Int, t1: Int,
  u1: Int, v1: Int, w1: Int, x1: Int, y1: Int, z1: Int,
  a2: Int, b2: Int, c2: Int, d2: Int, e2: Int, f2: Int, g2: Int, h2: Int, i2: Int, j2: Int,
  k2: Int, l2: Int, m2: Int, n2: Int, o2: Int, p2: Int, q2: Int, r2: Int, s2: Int, t2: Int
)


val x = productInstance[BigCaseClass]

Seems like this commit is what changed the behavior.

Since library authors are expected to stay on 3.3.x, it might be worth to actively point this out to any authors of libs that could improve the "max-inlines"-behavior of their lib. (Good thing is: the new behavior will apply if a lib using summonAll that is compiled with 3.3.x is used from a project using 3.4.0 or higher.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment