Reputation: 319
I'm seeing the following behaviour in scala 2.12.9:
case class A()
case class B()
trait X {
type T
def handle: PartialFunction[Any, Unit] = {
case in: T =>
println(s"is a T, $in")
case m =>
print(s"not a T, $m")
}
}
class X1 extends X {
override type T = A
}
object Cast extends App {
val x1 = new X1()
x1.handle(B())
}
The output is is a T, B(). I expected it to be "not a T", since I have overriden the type parameter of X to A. What am I missing?
Update: I wasn't aware that type members (not just type parameters) are also affected by type erasure. Thank you all! @DmytroMitin's answer workarounds work perfectly.
Upvotes: 1
Views: 99
Reputation: 51703
It's because of type erasure.
During compilation there is a warning
abstract type pattern X.this.T is unchecked since it is eliminated by erasure
Try
import shapeless.TypeCase
trait X {
type T
val OfTypeT: TypeCase[T] // abstract val, not def
def handle: PartialFunction[Any, Unit] = {
case OfTypeT(in) =>
println(s"is a T, $in")
case m =>
print(s"not a T, $m")
}
}
class X1 extends X {
override type T = A
override val OfTypeT = TypeCase[A]
}
val x1 = new X1()
x1.handle(B()) // not a T, B()
See Ways to pattern match generic types in Scala https://gist.github.com/jkpl/5279ee05cca8cc1ec452fc26ace5b68b (another copy of a blog post: http://web.archive.org/web/20171013214913/http://www.cakesolutions.net/teamblogs/ways-to-pattern-match-generic-types-in-scala)
If T
were a type parameter rather than type member we could use a context bound instead of overriding
import shapeless.{TypeCase, Typeable}
abstract class X[T: Typeable] {
private val OfTypeT = TypeCase[T]
def handle: PartialFunction[Any, Unit] = {
case OfTypeT(in) =>
println(s"is a T, $in")
case m =>
print(s"not a T, $m")
}
}
class X1 extends X[A]
val x1 = new X1()
x1.handle(B()) // not a T, B()
Or we could use TypeTag
import scala.reflect.runtime.universe.{TypeTag, typeOf, Type}
def getType[T: TypeTag](t: T): Type = typeOf[T]
abstract class X[T: TypeTag] {
def handle: PartialFunction[Any, Unit] = {
case in if typeOf[T] =:= getType(in) =>
println(s"is a T, $in")
case m =>
print(s"not a T, $m")
}
}
class X1 extends X[A]
val x1 = new X1()
x1.handle(B()) // not a T, B()
or ClassTag
import scala.reflect.ClassTag
abstract class X[T: ClassTag] {
def handle: PartialFunction[Any, Unit] = {
case in: T =>
println(s"is a T, $in")
case m =>
print(s"not a T, $m")
}
}
class X1 extends X[A]
val x1 = new X1()
x1.handle(B()) // not a T, B()
Upvotes: 3