Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Scala how do I filter by reified types at runtime?

I have a Scala collection that contains objects of different subtypes.

abstract class Base

class A extends Base

class B extends Base

val a1 = new A()
val a2 = new A()
val b = new B()
val s = List(a1, a2, b)

I'd like to filter out all the A objects or the B objects. I can do this easily if I know the object I want to filter on at compile time.

s.filter(_.isInstanceOf[A]) // Give me all the As
s.filter(_.isInstanceOf[B]) // Give me all the Bs

Can I do it if I only know the object type to filter on at runtime? I want to write a function like this.

def filterType(xs:List[Base], t) = xs.filter(_.isInstanceOf[t])

Where t indicates whether I want objects of type A or B.

Of course I can't actually write it this way because of type erasure. Is there an idiomatic Scala way to work around this using type tags? I've been reading the Scala type tag documentation and relevant StackOverflow posts, but I can't figure it out.

like image 704
W.P. McNeill Avatar asked May 03 '14 00:05

W.P. McNeill


People also ask

How do you find the type of a variable in Scala?

Use the getClass Method in Scala The getClass method in Scala is used to get the class of the Scala object. We can use this method to get the type of a variable. The output above shows that it prints java.

What is type in Scala?

Scala is a statically typed programming language. This means the compiler determines the type of a variable at compile time. Type declaration is a Scala feature that enables us to declare our own types.


2 Answers

This has come up a few times. Duplicate, anyone?

scala> trait Base
defined trait Base

scala> case class A(i: Int) extends Base 
defined class A

scala> case class B(i: Int) extends Base 
defined class B

scala> val vs = List(A(1), B(2), A(3))
vs: List[Product with Serializable with Base] = List(A(1), B(2), A(3))

scala> def f[T: reflect.ClassTag](vs: List[Base]) = vs collect { case x: T => x }
f: [T](vs: List[Base])(implicit evidence$1: scala.reflect.ClassTag[T])List[T]

scala> f[A](vs)
res0: List[A] = List(A(1), A(3))
like image 129
som-snytt Avatar answered Sep 21 '22 20:09

som-snytt


Type erasure will destroy any information in type parameters, but objects still know what class they belong to. Because of this, we cannot filter on arbitrary types, but we can filter by class or interface/trait. ClassTag is preferable to TypeTag here.

import scala.reflect.ClassTag

def filterType[T: ClassTag](xs: List[Base]) = xs.collect {
  case x: T => x
}

Which we can use like:

scala> filterType[B](s)
res29: List[B] = List(B@42096939)

scala> filterType[Base](s)
res30: List[Base] = List(A@8dbc09c, A@625f8cc7, B@42096939)

This method is safe at run-time if type T is not generic. If there was a class C[T] extends Base we could not safely filter on C[String].

like image 42
wingedsubmariner Avatar answered Sep 22 '22 20:09

wingedsubmariner