Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala asInstanceOf with parameterized types

Tags:

scala

I would like to write a function that casts to type A, where A can be e.g. List[Int], or a more complicated parameterized type like Map[Int, List[Int]].

def castToType[A](x: Any): A = {   // throws if A is not the right type   x.asInstanceOf[A] } 

Right now, due to type erasure (I believe), the code merrily works even when the type is not correct. The error only manifests on access, witha ClassCastException.

val x = List(1, 2, 3) val y = castToType[List[String]](x) y(0) --> throws java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String 

Is there a way I can use manifests to make this work properly? Thanks!

like image 763
Pandora Lee Avatar asked Jul 13 '11 23:07

Pandora Lee


2 Answers

Unfortunately, this in an inherent limitation of asInstanceOf. I'm actually surprised to see the scaladoc mention it in details:

Note that the success of a cast at runtime is modulo Scala's erasure semantics. Therefore the expression 1.asInstanceOf[String] will throw a ClassCastException at runtime, while the expression List(1).asInstanceOf[List[String]] will not. In the latter example, because the type argument is erased as part of compilation it is not possible to check whether the contents of the list are of the requested type.

If you're mainly concerned about failing fast on wrong cast for traversable which would likely be the main issue when getting stuff back from your DB/memcached interface, I was playing around forcing a cast of the head for traversable objects:

def failFastCast[A: Manifest, T[A] <: Traversable[A]](as: T[A], any: Any) = {    val res = any.asInstanceOf[T[A]]   if (res.isEmpty) res    else {      manifest[A].newArray(1).update(0, res.head) // force exception on wrong type     res   } } 

On a simple example it works:

scala> val x = List(1, 2, 3): Any x: Any = List(1, 2, 3)  scala> failFastCast(List[String](), x) java.lang.ArrayStoreException: java.lang.Integer [...]  scala> failFastCast(List[Int](), x) res22: List[Int] = List(1, 2, 3) 

But not on a more complex one:

val x = Map(1 -> ("s" -> 1L)): Any failFastCast(Map[Int, (String, String)](), x) // no throw 

I wonder if there is a way to recursively drill down into A to keep casting until there is no more type parameters...

like image 84
huynhjl Avatar answered Sep 24 '22 14:09

huynhjl


You are indeed correct - type erasure means that you cannot "cast" in such a way as to distinguish between List[Int] and List[String], for example. However, you can improve on the cast which you are performing, whereby A is erased in such a way as to mean that you cannot distinguish between an Int and a String:

def cast[A](a : Any) = a.asInstanceOf[A] //... is erased to def erasedCast(a : Any) = a.asInstanceOf[Any] 

What you need are reified generics, using manifests

def cast[A <: AnyRef : Manifest](a : Any) : A    = manifest[A].erasure.cast(a).asInstanceOf[A] 

Whilst the final cast is erased to AnyRef, at least you should have the correct Class[_] instance (manifest.erasure) to get the top level type correct. In action:

scala> cast[String]("Hey") res0: String = Hey  scala> cast[java.lang.Integer]("Hey")   java.lang.ClassCastException     at java.lang.Class.cast(Class.java:2990)     at .cast(<console>:7)     at .<init>(<console>:9)  scala> cast[List[String]](List("Hey")) res2: List[String] = List(Hey)  scala> cast[List[Int]](List("Hey")) res3: List[Int] = List(Hey) 

My advice is not to use nested reflection to decide whether the target was really a List[Int]: this is not generally feasible. For what should the following return?

cast[List[Int]](List[AnyVal](1, 2)) 
like image 27
oxbow_lakes Avatar answered Sep 22 '22 14:09

oxbow_lakes