Is there a built-in, more elegant way to filter and map collections by item type?

If I want to narrow, say, Iterable[A] for all elements of a certain type (for example, String ), I can do:

 as filter { _.isInstanceOf[String] } 

However, obviously, it is advisable to use this as an Iterable[String] , which can be done using map :

 as filter { _.isInstanceOf[String] } map { _.asInstanceOf[String] } 

This is pretty ugly. Of course, I could use flatMap instead:

 as flatMap[String] { a => if (a.isInstanceOf[String]) Some(a.asInstanceOf[String]) else None } 

But I'm not sure if this is more readable! I wrote a narrow function that can be used with implicit conversions:

 as.narrow(classOf[String]) 

But I was wondering if there is a better built-in mechanism that I forgot. In particular, it would be nice to narrow down a List[A] to a List[String] , and not to Iterable[String] , as it will be with my function.

+4
source share
4 answers

The Saxon Scala syntax for isInstanceOf / asInstanceOf is pattern matching:

 as flatMap { case x: String => Some(x); case _ => None } 

Since this uses flatMap , it should return the same set that you should start with.

In Scala 2.8, there is an experimental function that executes such a pattern defined inside a PartialFunction object. So, on Scala 2.8 you can do:

 as flatMap (PartialFunction.condOpt(_ : Any) { case x: String => x }) 

Which looks more mainly because I did not import this function in the first place. But, again, on Scala 2.8 there is a more direct way to do this:

 as collect { case x: String => x } 
+8
source

For the record here, the full implementation is narrow . Unlike the signature given in the question, it uses implicit Manifest to avoid some characters:

 implicit def itrToNarrowSyntax[A](itr: Iterable[A]) = new { def narrow[B](implicit m: Manifest[B]) = { itr flatMap { x => if (Manifest.singleType(x) <:< m) Some(x) else None } } } val res = List("daniel", true, 42, "spiewak").narrow[String] res == Iterable("daniel", "spiewak") 

Unfortunately, narrowing to a specific type (for example, List[String] ) rather than Iterable[String] bit more complicated. This can be done using the new collections API in Scala 2.8.0, using higher types, but not in the current structure.

+3
source

You can use in the future:

 for(a :Type <- itr) yield a 

But now it does not work. For more information, follow the links: http://lampsvn.epfl.ch/trac/scala/ticket/1089 http://lampsvn.epfl.ch/trac/scala/ticket/900

+1
source

Saving the form: I was a little hasty right now, so I leave the cast there, but I am sure that it can be eliminated. This works in the trunk:

 import reflect.Manifest import collection.Traversable import collection.generic.CanBuildFrom import collection.mutable.ListBuffer object narrow { class Narrower[T, CC[X] <: Traversable[X]](coll: CC[T])(implicit m1: Manifest[CC[T]], bf: CanBuildFrom[CC[T], T, CC[T]]) { def narrow[B: Manifest]: CC[B] = { val builder = bf(coll) def isB(x: T): Option[T] = if (Manifest.singleType(x) <:< manifest[B]) Some(x) else None coll flatMap isB foreach (builder += _) builder mapResult (_.asInstanceOf[CC[B]]) result } } implicit def toNarrow[T, CC[X] <: Traversable[X]](coll: CC[T])(implicit m1: Manifest[CC[T]], bf: CanBuildFrom[CC[T], T, CC[T]]) = new Narrower[T,CC](coll) def main(args: Array[String]): Unit = { println(Set("abc", 5, 5.5f, "def").narrow[String]) println(List("abc", 5, 5.5f, "def").narrow[String]) } } 

Launch:

 Set(abc, def) List(abc, def) 
+1
source

All Articles