I have read TypeTag
related article, but I am unable to realize filter a collection by elements type.
Example:
trait A
class B extends A
class C extends A
val v = Vector(new B,new C)
v filter ( _.isInstanceOf[B] )
The code above works fine.
However I want to extract filter
out of v
. E.g.
def filter[T,T2](data:Traversable[T2]) = (data filter ( _.isInstanceOf[T])).asInstanceOf[Traversable[T]]
//Then filter v by
filter[B,A](v)
In this case I get warning abstract type T is unchecked since it is eliminated by erasure
. I tried to use TypeTag
, but it seems not easy to get Type
on runtime.
Is there any elegant solution to realize function filter
?
Any solution via scala macro is also acceptable.
You need to provide a ClassTag
, not a TypeTag
, and use pattern matching. ClassTags
work well with pattern matching. You can even use the collect
method to perform the filter
and map
together:
def filter[T, T2](data: Traversable[T2])(implicit ev: ClassTag[T]) = data collect {
case t: T => t
}
For example:
val data = Seq(new B, new B, new C, new B)
filter[B, A](data) //Traversable[B] with length 3
filter[C, A](data) //Traversable[C] with length 1
One problem with this is that it might not work as expected with nested generic types.
The collect
method takes in a parameter of type PartialFunction
, representing a function that does not need to be defined on the entire domain. When using collect
elements where the PartialFunction
is undefined are filtered out, and elements that matched some case
statement are mapped accordingly.
You can also use existential types and let the compiler deduce the type of the data
parameter for a more concise syntax. You can also use context bounds:
def filter[T : ClassTag](data: Traversable[_]) = data collect { case t: T => t }
filter[B](data)
One problem with the methods here is that there is a significant difference between the native filter
method you have: these methods always returns a Traversable
while the native filter
returns the best type it can. For example:
val data = Vector(new B, new B, new C, new B)
data filter { _.isInstanceOf[B] } //Vector[A]
data filter { _.isInstanceOf[B] } map { _.asInstanceOf[B] } //Vector[B]
data collect { case t: B => t } //Vector[B]. Note that if you know the type at the calling point, this is pretty concise and might not need a helper method at all
//As opposed to:
filter[B](data) //Traversable[B], not a Vector!
You can fix this by using the CanBuildFrom
pattern using another implicit parameter. You can also use implicit classes to essentially add the method to the class (as opposed to calling the method in the static style shown above). This all adds up to a pretty complicated method, but I'll leave it here if you're interested in these enhancements:
implicit class RichTraversable[T2, Repr <: TraversableLike[T2, Repr], That](val trav: TraversableLike[T2, Repr]) extends AnyVal {
def withType[T : ClassTag](implicit bf: CanBuildFrom[Repr, T, That]) = trav.collect {
case t: T => t
}
}
This would allow you to do:
data.withType[B] //Vector[B], as desired