ClassCastException in a higher order generic function

I have code that tries to wrap a function in another that does dynamic type checking:

class Base class Foo extends Base class Bar extends Base object Main{ def checker[A <: Base]( func : A => String) : Base => String = (b : Base) => b match { case a : A => func(a) case _ => "error" } def fooFunc(f : Foo) = "It a foo" def main(arg : Array[String]) { val check = checker(fooFunc) println(check(new Foo) + ", " + check(new Bar)) } } 

This results in the following error:

 Exception in thread "main" java.lang.ClassCastException: Bar cannot be cast to Foo at Main$$anonfun$1.apply(Main.scala:17) at Main$.main(Main.scala:19) at Main.main(Main.scala) 

If I remove the type parameter and replace A with Foo in the checker definition, it works well. However, if I save the type parameter, but omit the function argument and replace func (a) with “good”, I get “good” for both Foo and Bar.

Is this what is called style erasure? I am not very familiar with this concept.
In addition, I would like to hear a decision around this.

+4
source share
3 answers

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.

+2
source

I found a way to use manifests.

 class Base class Foo extends Base class Bar extends Base trait Functor[A] { def apply[B](b : B)(implicit mb : Manifest[B]) : A } case class Checker[A](func : A => String)(implicit manifest : Manifest[A]) extends Functor[String]{ def apply[B](b : B)(implicit mb : Manifest[B]) = { if (mb == manifest) func(b.asInstanceOf[A]) else "error" } } object Main{ def fooFunc(f : Foo) = "good" def main(arg : Array[String]) { val check = Checker(fooFunc) println(check(new Foo) + ", " + check(new Bar)) } } 

I would love to hear suggestions from someone who knows what they are doing, though.

+3
source

as you defined it, checker can only accept a function that accepts Foo.

If you also create a shared fooFunc file, it should work:

 def fooFunc[A <: Base](f : A) = "It a foo" 

but then fooFunc will not be a suitable name, since it can return everything that comes from Base.

 def baseFunc[A <: Base](f : A) = "It a "+f.getClass 

maybe what you are looking for

EDIT

 class Base class Foo extends Base class Bar extends Base def checker[A <: Base]( func : A => String) : Base => String = (b : Base) => b match { case a : A => func(a) case _ => "error" } def fooFunc[A <: Base](f : A) = "It a "+f.getClass.getName val check = checker(fooFunc) println(check(new Foo) + ", " + check(new Bar)) 
+1
source

Source: https://habr.com/ru/post/1400712/


All Articles