Yes, you are in the country of erasure.
In the first case (source code), the compiler knows that A is Foo , but at run time the type parameter is erased to the upper type constraint, which in this example is Base (if you do not specify the upper type binding, the type parameter is erased to Object ). Therefore, the JVM sees your code as follows (do not notice the type parameterization):
def checker(func: Base => String): Base => String = (b: Base) => b match { case a : Base => func(a) case _ => "error" }
Any Foo or Bar object will match Base , and then the JVM will try to apply it to Foo and call func . Works if b is an object of class Foo , but throws an exception for Bar . No surprises.
In the second case, you remove the type parameter and replace A with Foo in the checker definition. Thus, your code at runtime looks like this (the function is not parameterized from the beginning, so nothing is erased):
def checker(func: Foo => String): Base => String = (b: Base) => b match { case a: Foo => func(a) case _ => "error" }
This works as expected, but fixed only for Foo . Therefore, you need to write a separate checker for Bar and any other type that you want to check.
In the third case, you save the type parameter, but omit the function argument and replace func(a) with "good" . This leads to a similar code to case 1 (erase from Foo to Base again), except that func not called, so casting to Foo not required. Therefore, no exception.
def checker: Base => String = (b: Base) => b match { case a: Base => "good" case _ => "error" }
It’s just that any object that you pass (a subclass of the database) is matched to the first sentence, and checker always returns “good”.
Now the decision. You were on the right track with Manifest (but the code you showed is too complicated for what you are trying to achieve). When you request a Manifest Scala, the compiler passes an additional object to a method / function that you can use at run time to "restore" the erased types (s). Below I use 'context bound' instead of specifying it as (implicit manifest : Manifest[A]) , but this is the same, in short.
def checker[A <: Base: Manifest](func: A => String): Base => String = (b: Base) => if(manifest[A].erasure == b.getClass) func(b.asInstanceOf[A]) else "error"
So, when you call it like this:
def fooFunc(f: Foo) = "It a foo" def barFunc(f: Bar) = "It a bar" def main(arg: Array[String]) { val check1 = checker(fooFunc) val check2 = checker(barFunc) println(check1(new Foo) + ", " + check1(new Bar)) println(check2(new Foo) + ", " + check2(new Bar)) }
You will get the result as expected:
It a foo, error error, It a bar
Erasure is the source of all kinds of entertainment in Scala, since type parameterization is more common here than in Java. No way, I recommend exploring everything you can.