Chris Mukherjee
Chris Mukherjee

Reputation: 841

Scala: isInstanceOf followed by asInstanceOf

In my team, I often see teammates writing

list.filter(_.isInstanceOf[T]).map(_.asInstanceOf[T])

but this seems a bit redundant to me.

If we know that everything in the filtered list is an instance of T then why should we have to explicitly cast it as such?


I know of one alternative, which is to use match.

eg:

list.match {
  case thing: T => Some(thing)
  case _ => None
}

but this has the drawback that we must then explicitly state the generic case.


So, given all the above, I have 2 questions:

1) Is there another (better?) way to do the same thing?

2) If not, which of the two options above should be preferred?

Upvotes: 4

Views: 1008

Answers (2)

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149628

but this seems a bit redundant to me.

Perhaps programmers in your team are trying to shield that piece of code from someone mistakenly inserting a type other then T, assuming this is some sort of collection with type Any. Otherwise, the first mistake you make, you'll blow up at run-time, which is never fun.

I know of one alternative, which is to use match.

Your sample code won't work because of type erasure. If you want to match on underlying types, you need to use ClassTag and TypeTag respectively for each case, and use =:= for type equality and <:< for subtyping relationships.

Is there another (better?) way to do the same thing?

Yes, work with the type system, not against it. Use typed collections when you can. You haven't elaborated on why you need to use run-time checks and casts on types, so I'm assuming there is a reasonable explanation to that.

If not, which of the two options above should be preferred?

That's a matter of taste, but using pattern matching on types can be more error-prone since one has to be aware of the fact that types are erased at run-time, and create a bit more boilerplate code for you to maintain.

Upvotes: 2

Kolmar
Kolmar

Reputation: 14224

You can use collect:

list collect {
  case el: T => el
}

Real types just work (barring type erasure, of course):

scala> List(10, "foo", true) collect { case el: Int => el } 
res5: List[Int] = List(10)

But, as @YuvalItzchakov has mentioned, if you want to match for an abstract type T, you must have an implicit ClassTag[T] in scope.

So a function implementing this may look as follows:

import scala.reflect.ClassTag

def filter[T: ClassTag](list: List[Any]): List[T] = list collect {
  case el: T => el
} 

And using it:

scala> filter[Int](List(1, "foo", true))
res6: List[Int] = List(1)

scala> filter[String](List(1, "foo", true))
res7: List[String] = List(foo)

collect takes a PartialFunction, so you shouldn't provide the generic case.

But if needed, you can convert a function A => Option[B] to a PartialFunction[A, B] with Function.unlift. Here is an example of that, also using shapeless.Typeable to work around type erasure:

import shapeless.Typeable
import shapeless.syntax.typeable._

def filter[T: Typeable](list: List[Any]): List[T] = 
  list collect Function.unlift(_.cast[T])

Using:

scala> filter[Option[Int]](List(Some(10), Some("foo"), true))
res9: List[Option[Int]] = List(Some(10))

Upvotes: 10

Related Questions