56
votes

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

2

2 Answers

76
votes

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)
12
votes
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]