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?
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.
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
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.
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
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.
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).
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.
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?
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
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))
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
}
Related
Is there a generic way to force SeqView to Seq?
In Scala 2.12, SeqView extends Seq, so you can end up with laziness when one might want strict collections.
I was trying to generically force SeqViews to Seqs, but am running into type errors that I'm not familiar with:
import scala.collection.SeqView
def force(xs: Seq[Int]): Seq[Int] = xs match {
case view: SeqView[_,_] => view.force
case other => other
}
force(List(1, 2, 3))
force(List(1, 2, 3).view)
Cannot construct a collection of type That with elements of type _ based on a collection of type _.
Scastie link: https://scastie.scala-lang.org/ThMW8jixT7Odet17EiDavA
Notably, I can provide type parameters to the match, but of course, I get type erasure warnings
import scala.collection.SeqView
def force(xs: Seq[Int]): Seq[Int] = xs match {
case view: SeqView[Int, Seq[Int]] => view.force
case other => other
}
force(List(1, 2, 3))
force(List(1, 2, 3).view)
The type erasure warning scares me, but it seems to work.
Can anyone shed some light on this?
By using Seq[_,_] in patten matching you made compiler unable to find right implicit for type class CanBuildFrom.
The workaround I came up with, to not have a warning and allow compiler resolve CanBuildFrom is just to match against SeqView[_, _] and then use asInstanceOf to return to original type:
import scala.collection.SeqView
def force[A](xs: Seq[A]): Seq[A] = xs match {
case view: SeqView[_, _] => view.asInstanceOf[SeqView[A, Seq[A]]].force
case other: Seq[A] => other
}
println(force(List(1, 2, 3)))
println(force(List(1, 2, 3).view))
I also added the type parameter A instead of fixed Int.
Another possibility is just to annotate types with #unchecked:
def force[A](xs: Seq[A]): Seq[A] = xs match {
case view: SeqView[A#unchecked, Seq[A]#unchecked] => view.force
case other: Seq[A] => other
}
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?
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.
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
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.
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
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.
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).
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.
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?
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
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))
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
}
I've got a sequence Seq[Any] that has a variety of objects in it (like String, Integer, List[String], etc). I'm trying to sift through the list and break it up into separate lists partitioned based on the class type. The following is a pattern I'm using in the code:
val allApis = mySequence.filter(_.isInstanceOf[String])
This works well and doesn't generate any warnings. However, when I try to do the same for filtering out the objects that are Lists of strings:
val allApis = mySequence.filter(_.isInstanceOf[List[String]])
I get a warning that says non-variable type argument String in type List[String] is unchecked since it is eliminated by erasure. Now, the technique actually works and I'm able to comfortably filter the sequence as desired, but I'm wondering what is the appropriate way to deal with the warning in an idiomatic way so that I know I don't have a serious bug lurking in the background waiting to blow up
It doesn't work because it will pick out List[Double] or any other list in addition to List[String]. There are a variety of ways of fixing the problem, including wrapping any parameterized types in a non-parameterized case class:
case class StringList(value: List[String])
and then you can just
mySequence.collect{ case StringList(xs) => xs }
to pull out the lists of strings (with the correct type, and type-safely also).
Alternatively, if you want to not wrap objects and want to be sure that they're of the correct type, you can check every element:
mySequence.filter( _ match {
case xs: List[_] => xs.forall( _ match { case _: String => true; case _ => false })
case _ => false
})
though even this won't let you know which type empty lists were supposed to be.
Another possibility is to glue TypeTags to everything in your list; this will prevent you needing to manually wrap things. For instance:
import scala.reflect.runtime.universe.{TypeTag, typeTag}
def add[A](xs: List[(Any, TypeTag[_])], a: A)(implicit tt: TypeTag[A]) = (a, tt) :: xs
val mySequence = add(add(add(Nil, List(42)), true), List("fish"))
mySequence.filter(_._2.tpe weak_<:< typeTag[List[String]].tpe)
val v = 1 ::"abc" :: true :: Nil
v : List[Any] = List(1,abc,true)
type parameter of List type has been unified to the greatest common super type of the elements in the List which is Any.
Shapeless is to the rescue.
import shapeless._
import HList._
val s = 1 :: "abc" :: true: HNil
s : shapeless.::[Int,shapeless.::[String,shapelsss.::[Boolean,shapeless.HNil]]]
= 1 :: abc :: true :: HNil
With Shapeless HList you can get compile time safety for a heterogeneous list. you can now filter in a typesafe manner. e.g.
s.filter[String]
Let's suppose we have a generic class Container:
case class Container[+A](value: A)
We then want to pattern match a Container with a Double and a Container of Any:
val double = Container(3.3)
var container: Container[Any] = double
To do this, we would normally write:
container match {
case c: Container[String] => println(c.value.toUpperCase)
case c: Container[Double] => println(math.sqrt(c.value))
case _ => println("_")
}
However, the compiler gives two warnings, one for each of the first two cases. For example, the first warning says: "non-variable type argument String in type pattern Container[String] is unchecked since it is eliminated by erasure". Because of the erasure, it is impossible during runtime to distinguish between different kinds of containers and the first catch will be matched. As a consequence, container of type Container[Double] will be matched by the first case, which catches Container[String] objects, so toUpperCase method will be called on a Double and a java.lang.ClassCastException will be thrown.
How to match a Container parametrized by a particular type?
Maybe this will help
def matchContainer[A: Manifest](c: Container[A]) = c match {
case c: Container[String] if manifest <:< manifest[String] => println(c.value.toUpperCase)
case c: Container[Double] if manifest <:< manifest[Double] => println(math.sqrt(c.value))
case c: Container[_] => println("other")
}
Edit:
As Impredicative pointed out, Manifest is deprecated. Instead you could do the following:
import reflect.runtime.universe._
def matchContainer[A: TypeTag](c: Container[A]) = c match {
case c: Container[String] if typeOf[A] <:< typeOf[String] => println("string: " + c.value.toUpperCase)
case c: Container[Double] if typeOf[A] <:< typeOf[Double] => println("double" + math.sqrt(c.value))
case c: Container[_] => println("other")
}
In general rarry's answer is correct, for your case however it can be simplified, because your container only contains a single value of a generic type, so you can match on that value's type directly:
container match {
case Container(x: String) => println("string")
case Container(x: Double) => println("double")
case _ => println("w00t")
}
A possible workaround for this could be to use isInstanceOf and asInstanceOf.
container match {
case Container(x) if x.isInstanceOf[String] =>
println(x.asInstanceOf[String].toUpperCase)
case Container(x) if x.isInstanceOf[Double] =>
println(math.sqrt(x.asInstanceOf[Double]))
case _ => println("_")
}
This works, but it doesn't look elegant at all. Professor Martin Odersky, the creator of Scala, says that isInstanceOf and asInstanceOf should be avoided.
As Rob Norris pointed me out, on the forum of the course "Functional programming in Scala" from Coursera, matching by type is a bad practice: case foo: Bar => .... Scala encourages to take advantage of static typing and avoid checking type during runtime. This is consistent with the philosophy of Haskell/ML world. Instead of matching types, case clauses should match constructors.
To solve the Container matching problem, a special container for each type can be defined:
class Container[+A](val value: A)
case class StringContainer(override val value: String)
extends Container(value)
case class DoubleContainer(override val value: Double)
extends Container(value)
And now constructors will be matched, not types:
container match {
case StringContainer(x) => println(x.toUpperCase)
case DoubleContainer(x) => println(math.sqrt(x))
case _ => println("_")
}
Apparently, we could be defined unapply methods in two objects, StringContainer and DoubleContainer and use the same match as above, instead of extending the Container class:
case class Container[+A](val value: A)
object StringContainer {
def unapply(c: Container[String]): Option[String] = Some(c.value)
}
object DoubleContainer {
def unapply(c: Container[Double]): Option[Double] = Some(c.value)
}
But this does not work, again, because of JVM type erasure.
A reference to Rob Norris post, which lead me to this answer can be found here: https://class.coursera.org/progfun-002/forum/thread?thread_id=842#post-3567 . Unfortunately, you can't access it unless you are enrolled in the Coursera course.
Note: you also have an alternative with Miles Sabin's Shapeless library (already mentioned by Miles in 2012 here).
You can see an example in "Ways to pattern match generic types in Scala" from Jaakko Pallari
Typeable is a type class that provides the ability to cast values from Any type to a specific type.
The result of the casting operation is an Option where the Some value will contain the successfully casted value, and the None value represents a cast failure.
TypeCase bridges Typeable and pattern matching. It's essentially an extractor for Typeable instances
import shapeless._
def extractCollection[T: Typeable](a: Any): Option[Iterable[T]] = {
val list = TypeCase[List[T]]
val set = TypeCase[Set[T]]
a match {
case list(l) => Some(l)
case set(s) => Some(s)
case _ => None
}
}
val l1: Any = List(1, 2, 3)
val l2: Any = List[Int]()
val s: Any = Set(1, 2, 3)
extractCollection[Int](l1) // Some(List(1, 2, 3))
extractCollection[Int](s) // Some(Set(1, 2, 3))
extractCollection[String](l1) // None
extractCollection[String](s) // None
extractCollection[String](l2) // Some(List()) // Shouldn't this be None? We'll get back to this.
While Typeable may look like it has what it takes to solve type erasure, it's still subject to the same behaviour as any other runtime code.
This can be seen in the last lines of the previous code examples where empty lists were recognized as string lists even when they were specified to be integer lists. This is because Typeable casts are based on the values of the list. If the list is empty, then naturally that is a valid string list and a valid integer list (or any other list for that matter)
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?
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.
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
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.
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
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.
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).
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.
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?
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
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))
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
}