How do I get around type erasure on Scala? Or, why

2018-12-31 02:40发布

It's a sad fact of life on Scala that if you instantiate a List[Int], you can verify that your instance is a List, and you can verify that any individual element of it is an Int, but not that it is a List[Int], as can be easily verified:

scala> List(1,2,3) match {
     | case l : List[String] => println("A list of strings?!")
     | case _ => println("Ok")
     | }
warning: there were unchecked warnings; re-run with -unchecked for details
A list of strings?!

The -unchecked option puts the blame squarely on type erasure:

scala>  List(1,2,3) match {
     |  case l : List[String] => println("A list of strings?!")
     |  case _ => println("Ok")
     |  }
<console>:6: warning: non variable type-argument String in type pattern is unchecked since it is eliminated by erasure
        case l : List[String] => println("A list of strings?!")
                 ^
A list of strings?!

Why is that, and how do I get around it?

11条回答
其实,你不懂
2楼-- · 2018-12-31 03:05

There is a way to overcome the type erasure issue in Scala. In Overcoming Type Erasure in matching 1 and Overcoming Type Erasure in Matching 2 (Variance) are some explanation of how to code some helpers to wrap the types, including Variance, for matching.

查看更多
余欢
3楼-- · 2018-12-31 03:07

Since Java does not know the actual element type, I found it most useful to just use List[_]. Then the warning goes away and the code describes reality - it is a list of something unknown.

查看更多
人间绝色
4楼-- · 2018-12-31 03:14

I found a slightly better workaround for this limitation of the otherwise awesome language.

In Scala, the issue of type erasure does not occur with arrays. I think it is easier to demonstrate this with an example.

Let us say we have a list of (Int, String), then the following gives a type erasure warning

x match {
  case l:List[(Int, String)] => 
  ...
}

To work around this, first create a case class:

case class IntString(i:Int, s:String)

then in the pattern matching do something like:

x match {
  case a:Array[IntString] => 
  ...
}

which seems to work perfectly.

This will require minor changes in your code to work with arrays instead of lists, but should not be a major problem.

Note that using case a:Array[(Int, String)] will still give a type erasure warning, so it is necessary to use a new container class (in this example, IntString).

查看更多
无色无味的生活
5楼-- · 2018-12-31 03:17

You can use the Typeable type class from shapeless to get the result you're after,

Sample REPL session,

scala> import shapeless.syntax.typeable._
import shapeless.syntax.typeable._

scala> val l1 : Any = List(1,2,3)
l1: Any = List(1, 2, 3)

scala> l1.cast[List[String]]
res0: Option[List[String]] = None

scala> l1.cast[List[Int]]
res1: Option[List[Int]] = Some(List(1, 2, 3))

The cast operation will be as precise wrt erasure as possible given the in-scope Typeable instances available.

查看更多
梦该遗忘
6楼-- · 2018-12-31 03:17

Not a solution but a way to live with it without sweeping it under the rug altogether: Adding the @unchecked annotation. See here - http://www.scala-lang.org/api/current/index.html#scala.unchecked

查看更多
何处买醉
7楼-- · 2018-12-31 03:18

I wanted to add an answer which generalises the problem to: How do a get a String representation of the type of my list at runtime

import scala.reflect.runtime.universe._

def whatListAmI[A : TypeTag](list : List[A]) = {
    if (typeTag[A] == typeTag[java.lang.String]) // note that typeTag[String] does not match due to type alias being a different type
        println("its a String")
    else if (typeTag[A] == typeTag[Int])
        println("its a Int")

    s"A List of ${typeTag[A].tpe.toString}"
}

val listInt = List(1,2,3)
val listString = List("a", "b", "c")

println(whatListAmI(listInt))
println(whatListAmI(listString))
查看更多
登录 后发表回答