381
votes

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
Scala 2.8 Beta 1 RC4 just made some changes to how type erasure works. I'm not sure if this directly affects your question.Scott Morrison
That's just what types erasure to, that has changed. The short of it can be summed as "Proposal: The erasure of "Object with A" is "A" instead of "Object"." The actual specification is rather more complex. It's about mixins, at any rate, and this question is concerned about generics.Daniel C. Sobral
Thanks for the clarification -- I'm a scala newcomer. I feel like right now is a bad time to jump into Scala. Earlier, I could have learnt the changes in 2.8 from a good base, later I'd never have to know the difference!Scott Morrison
Here's a somewhat related question about TypeTags.pvorb
Running scala 2.10.2, I saw this warning instead: <console>:9: warning: fruitless type test: a value of type List[Int] cannot also be a List[String] (but still might match its erasure) case list: List[String] => println("a list of strings?") ^ I find your question and answer to be very helpful, but I'm not sure if this updated warning is useful to readers.Kevin Meredith

11 Answers

243
votes

This answer uses the Manifest-API, which is deprecated as of Scala 2.10. Please see answers below for more current solutions.

Scala was defined with Type Erasure because the Java Virtual Machine (JVM), unlike Java, did not get generics. This means that, at run time, only the class exists, not its type parameters. In the example, JVM knows it is handling a scala.collection.immutable.List, but not that this list is parameterized with Int.

Fortunately, there's a feature in Scala that lets you get around that. It’s the Manifest. A Manifest is class whose instances are objects representing types. Since these instances are objects, you can pass them around, store them, and generally call methods on them. With the support of implicit parameters, it becomes a very powerful tool. Take the following example, for instance:

object Registry {
  import scala.reflect.Manifest
  
  private var map= Map.empty[Any,(Manifest[_], Any)] 
  
  def register[T](name: Any, item: T)(implicit m: Manifest[T]) {
    map = map.updated(name, m -> item)
  }
  
  def get[T](key:Any)(implicit m : Manifest[T]): Option[T] = {
    map get key flatMap {
      case (om, s) => if (om <:< m) Some(s.asInstanceOf[T]) else None
    }     
  }
}

scala> Registry.register("a", List(1,2,3))

scala> Registry.get[List[Int]]("a")
res6: Option[List[Int]] = Some(List(1, 2, 3))

scala> Registry.get[List[String]]("a")
res7: Option[List[String]] = None

When storing an element, we store a "Manifest" of it too. A Manifest is a class whose instances represent Scala types. These objects have more information than JVM does, which enable us to test for the full, parameterized type.

Note, however, that a Manifest is still an evolving feature. As an example of its limitations, it presently doesn't know anything about variance, and assumes everything is co-variant. I expect it will get more stable and solid once the Scala reflection library, presently under development, gets finished.

102
votes

You can do this using TypeTags (as Daniel already mentions, but I'll just spell it out explicitly):

import scala.reflect.runtime.universe._
def matchList[A: TypeTag](list: List[A]) = list match {
  case strlist: List[String @unchecked] if typeOf[A] =:= typeOf[String] => println("A list of strings!")
  case intlist: List[Int @unchecked] if typeOf[A] =:= typeOf[Int] => println("A list of ints!")
}

You can also do this using ClassTags (which saves you from having to depend on scala-reflect):

import scala.reflect.{ClassTag, classTag}
def matchList2[A : ClassTag](list: List[A]) = list match {
  case strlist: List[String @unchecked] if classTag[A] == classTag[String] => println("A List of strings!")
  case intlist: List[Int @unchecked] if classTag[A] == classTag[Int] => println("A list of ints!")
}

ClassTags can be used so long as you don't expect the type parameter A to itself be a generic type.

Unfortunately it's a little verbose and you need the @unchecked annotation to suppress a compiler warning. The TypeTag may be incorporated into the pattern match automatically by the compiler in the future: https://issues.scala-lang.org/browse/SI-6517

65
votes

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.

17
votes

I came up with a relatively simple solution that would suffice in limited-use situations, essentially wrapping parameterized types that would suffer from the type erasure problem in wrapper classes that can be used in a match statement.

case class StringListHolder(list:List[String])

StringListHolder(List("str1","str2")) match {
    case holder: StringListHolder => holder.list foreach println
}

This has the expected output and limits the contents of our case class to the desired type, String Lists.

More details here: http://www.scalafied.com/?p=60

14
votes

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.

11
votes

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).

6
votes

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
votes

I'm wondering if this is a suited workaround:

scala> List(1,2,3) match {
     |    case List(_: String, _*) => println("A list of strings?!")
     |    case _ => println("Ok")
     | }

It does not match the "empty list" case, but it gives a compile error, not a warning!

error: type mismatch;
found:     String
requirerd: Int

This on the other hand seems to work....

scala> List(1,2,3) match {
     |    case List(_: Int, _*) => println("A list of ints")
     |    case _ => println("Ok")
     | }

Isn't it kinda even better or am I missing the point here?

1
votes

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

0
votes

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))
-20
votes

Using pattern match guard

    list match  {
        case x:List if x.isInstanceOf(List[String]) => do sth
        case x:List if x.isInstanceOf(List[Int]) => do sth else
     }