Type mismatch on abstract type used in pattern matching
The first case is unsound because you underestimate the variety of types in Scala type system. It would make sense if, when we took case i:Int
branch we knew T
was Int
, or at least a supertype of Int
. But it doesn't have to be! E.g. it could be 42.type or a tagged type.
There's no such problem in the second case, because from IntExpr <: Expr[T]
, the compiler does know T
must be exactly Int
.
You ask of your function to return a type T
, then you pattern-match against Int
and Boolean
.
Except your function has no evidence that Int
and Boolean
are also of type T
: when you pattern-match, you introduce the constraint that Int <: T
and Boolean <: T
.
You could either replace the return type T
by a fixed type like String
and return a String, or introduce a constraint that will satisfy both the case Int
and Boolean
.
//this compiles
def f1[T](e: T ): String = e match {
case _:Int => "integer"
case _:Boolean => "boolean"
}
//this compiles too, but will return AnyVal
def f1[T >: AnyVal](e: T ): T = e match {
case i:Int => i
case b:Boolean => b
}
Basically you can't just return any type T
dynamically because you need to prove at compile time that your function type-checks out.
The other function in your example avoids the issue by encapsulating type constraints within case classes IntExpr <: Expr[Int]
and BoolExpr <: Expr[Boolean]
(notice how Expr[_]
would be the equivalent of T
in the constraints I mentioned above). At compile time, T is properly identified in all case
s (e.g in the IntExpr
you know it's an Int
)
In addition to @Esardes answer, this worked by defining a type bound for T
:
scala> def f1[T >: AnyVal](e: T):T = e match {
| case i:Int => i
| case b:Boolean => b
| }
f1: [T >: AnyVal](e: T)T
scala> f1(1)
res3: AnyVal = 1
scala> f1(true)
res4: AnyVal = true