Ben Horner
Ben Horner

Reputation: 319

How can I reusably filter based on type in Scala?

I'm parsing a file which contains various structures, one of which is a map with heterogeneous values. After the map is parsed into memory, I would like to filter it based on the value types, to get the submap for a given type.

For sake of conversation here is a simple analogous example:

// the types I want to filter on
case class A(c: Char)
case class B(c: Char)

// an example for testing
val m: Map[Int, Any] = Map((1 -> A('x')), (2 -> B('p')),
                           (3 -> A('y')), (4 -> B('q')))

And here is a function to filter the map down to a Map[Int, A]:

// a successful filter function based on A
def as(m: Map[Int, Any]): Map[Int, A] =
  for((int, a: A) <- m) yield (int -> a)

And you can imagine the practically identical function "bs" which is also successful, but I didn't want to write. Instead, I thought I would write a generic function:

// a failed generic filter function
def typeFilter[T](m: Map[Int, Any]): Map[Int, T] =
  for((int, t: T) <- m) yield (int -> t)

So, this is the status:

val aMap: Map[Int, A] = as(m) // works!
val bMap: Map[Int, B] = bs(m) // works!
val aMapGen: Map[Int, A] = typedFilter[A](m) // doesn't work! returns all of m
val bMapGen: Map[Int, B] = typedFilter[B](m) // doesn't work! returns all of m

Now that I've been more rigorous about this, to enter this question, it seems even more strange. How can a Map[Int, A] contain mappings to values of B? The fact that it compiles as declared seems to imply that it should function correctly, but when I print the contents of either aMapGen, or bMapGen, I see the entire contents of m, including values having incompatible types. This is the first problem like this I've run into in Scala, like the frustrations with type-erasure in Java.

I would love an explanation of why this is behaving as it is, but my primary objective is to be able to write some reusable code to do this filtering based on types. Otherwise I'll have to copy/paste the function with altered types for all of the types in my list.

Thanks for any help.

Upvotes: 3

Views: 435

Answers (3)

Arseniy Zhizhelev
Arseniy Zhizhelev

Reputation: 2401

  1. Doesn't the compiler warn about type erasure at the line

    for((int, t: T) <- m) yield (int -> t)
    

    ?

  2. For checking classes at runtime one may use implicit classTag:

    def typeFilter[T](m: Map[Int, Any])(implicit classTag:ClassTag[T]): Map[Int, T] =
      m.collect{ case (int, t:T) if classTag.isInstance(t) => (int -> t)}
    

Upvotes: 1

som-snytt
som-snytt

Reputation: 39577

Is this an improvement?

scala> // an example for testing

scala> val m: Map[Int, Any] = Map((1 -> A('x')), (2 -> B('p')),
     |                            (3 -> A('y')), (4 -> B('q')))
m: Map[Int,Any] = Map(1 -> A(x), 2 -> B(p), 3 -> A(y), 4 -> B(q))

scala> def typeFilter[T](m: Map[Int, Any]): Map[Int, T] =
     |   for((int, t: T) <- m) yield (int -> t)
<console>:14: warning: abstract type pattern T is unchecked since it is eliminated by erasure
         for((int, t: T) <- m) yield (int -> t)
                      ^
typeFilter: [T](m: Map[Int,Any])Map[Int,T]

scala> import reflect._
import reflect._

scala> def typeFilter[T: ClassTag](m: Map[Int, Any]): Map[Int, T] =
     | for((int, t: T) <- m) yield (int -> t)
typeFilter: [T](m: Map[Int,Any])(implicit evidence$1: scala.reflect.ClassTag[T])Map[Int,T]

scala> typedFilter[A](m)
<console>:17: error: not found: value typedFilter
              typedFilter[A](m)
              ^

scala> typeFilter[A](m)
res3: Map[Int,A] = Map(1 -> A(x), 3 -> A(y))

scala> typeFilter[B](m)
res4: Map[Int,B] = Map(2 -> B(p), 4 -> B(q))

People have asked for the ability to -Xfatal-warnings for particular kinds of warnings.

Until that day arrives, maybe always compile with -Xlint -Xfatal-warnings and make your bed with clean linens.

Upvotes: 3

Alex
Alex

Reputation: 71

This is failing because of type erasure. Scala runs on the Java Virtual Machine, which does not have generics. Therefore, information about generics is not available at runtime, just like in Java.

For help getting around this issue, I refer you to How do I get around type erasure on Scala? Or, why can't I get the type parameter of my collections?

Upvotes: 3

Related Questions