This code compiles with an error:
def f1[T](e: T): T = e match {
case i:Int => i
case b:Boolean => b
}
// type mismatch;
// found : i.type (with
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 cases (e.g in the IntExpr you know it's an Int)