Open
Description
Compiler version
3.6.4
Minimized code
I'm building a toy typeclass (named Typeclass
) and I want it to support Union types, so I wrote a combine
method that takes a Typeclass[A]
and a Typeclass[B]
and returns a Typeclass[A | B]
. I'm using TypeTest
to be able to match the type at compile time.
When declaring a Typeclass instance for a case object, this sample code compiles correctly but generates a ClassCastException
.
import scala.reflect.TypeTest
trait Typeclass[A] {
def print(a: A): String
}
object Typeclass {
def apply[A: Typeclass]: Typeclass[A] = implicitly
def instance[A](fn: A => String): Typeclass[A] = fn(_)
def combine[A, B](
typeclassA: Typeclass[A],
typeclassB: Typeclass[B]
)(using
TypeTest[A | B, A],
TypeTest[A | B, B]
): Typeclass[A | B] =
Typeclass.instance[A | B](
{
case a: A => typeclassA.print(a)
case b: B => typeclassB.print(b)
}
)
}
case class CaseClassA(value: String = "A")
case class CaseClassB(value: String = "B")
case object CaseObject
val typeclass: Typeclass[CaseClassA | CaseClassB | CaseObject.type] =
Typeclass.combine(
Typeclass.instance[CaseClassA](_.value),
Typeclass.combine(
Typeclass.instance[CaseObject.type](_ => "C"),
Typeclass.instance[CaseClassB](_.value)
)
)
def print(elem: CaseClassA | CaseClassB | CaseObject.type) =
println(typeclass.print(elem))
print(CaseClassA())
print(CaseClassB())
print(CaseObject)
Output
By running scala-cli -S 3.6.4 classCastException.sc
, I get:
A
B
Exception in thread "main" java.lang.ClassCastException: class classCastException$_$CaseObject$ cannot be cast to class classCastException$_$CaseClassB (classCastException$_$CaseObject$ and classCastException$_$CaseClassB are in unnamed module of loader 'app')
at classCastException$_.$init$$$anonfun$7(classCastException.sc:38)
at classCastException$_.classCastException$_$Typeclass$$$_$combine$$anonfun$1(classCastException.sc:22)
at classCastException$_.classCastException$_$Typeclass$$$_$instance$$anonfun$1(classCastException.sc:10)
at classCastException$_.print(classCastException.sc:41)
at classCastException$_.<init>(classCastException.sc:45)
at classCastException_sc$.script$lzyINIT1(classCastException.sc:60)
at classCastException_sc$.script(classCastException.sc:60)
at classCastException_sc$.main(classCastException.sc:64)
at classCastException_sc.main(classCastException.sc)
Expectation
A
B
C
Workaround
If a declare typeclass
by switching CaseClassA
and CaseObject
typeclass instances, there's no exception and it gives the correct result
val typeclass: Typeclass[CaseClassA | CaseClassB | CaseObject.type] =
Typeclass.combine(
Typeclass.instance[CaseObject.type](_ => "C"),
Typeclass.combine(
Typeclass.instance[CaseClassA](_.value),
Typeclass.instance[CaseClassB](_.value)
)
)