Why does pattern matching on a Class's generic constructor parameter fail, while that on a method's generic parameter succeed?
I have a type class Ev:
sealed trait Ev[T]
case object EvInt extends Ev[Int]
case object EvStr extends Ev[String]
and a generic class like so:
class C[T](ev: Ev[T]) {
val id: Int = ev match {
case EvInt => 0
case EvStr => 1
}
}
The above pattern match on ev fails with:
error: pattern type is incompatible with expected type;
found : EvInt.type
required: Ev[T]
case EvInt => 0
whereas this works:
object C {
def id[T](ev: Ev[T]): Int = {
ev match {
case EvInt => 0
case EvStr => 1
}
}
}
For my purpose, I could work around this in various ways, e.g., by moving id into the Ev trait:
sealed trait Ev[T] {
def id: Int
}
class C[T](ev: Ev[T]) { val id: Int = ev.id }
but I am curious why it fails.
That makes sense to think that it should've worked in the first place. And as some people commented, scala 2 had some flaws in ADTs and type inference/checking. And you already know the workarounds to this problem. So I'm just going to write my "experiments"/assumptions here.
The compiler says it requires an Ev[T], but you're providing it with EvInt and EvString. But what I thought at first, was that it's trying to say under the hood that it requires T, but you're providing it with Int and String. And I thought why this is happening, is probably because of different rules to type inference in classes and methods and other identifiers. For instance, if you change your code to:
class C[T](ev: Ev[T]) {
// or use ev.asInstanceOf[Ev[_]]
val id: Int = (ev: Ev[_]) match {
case EvInt => 0
case EvStr => 1
}
}
Everything would work fine! Because you're basically telling the compiler there is no concrete T here.
Note that you wouldn't face the issue if you specify the type:
ev match {
case _: EvInt.type => 0
case _: EvString.type => 1
}
Also, instead of case objects (which at first extend their own type), if you use vals, the compilation error would go away:
val eInt: Ev[Int] = EvInt
val eStr: Ev[String] = EvString
val id: Int = ev match {
case eInt => 0
case eStr => 1
}
So in general, I think the compiler couldn't prove the relation between EvInt and EvString with Ev[*T*], and this problem of type inference was sort of common in scala 2. I'd say it's a bug, but I'm not confident about that.
Hope this helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With