Skip to content

Instantly share code, notes, and snippets.

@milessabin
Created January 10, 2012 02:44
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save milessabin/f83892f65f63b14a1f75 to your computer and use it in GitHub Desktop.
Save milessabin/f83892f65f63b14a1f75 to your computer and use it in GitHub Desktop.
filterMap implementation which abstracts over container type and follows the "same result type" principle
// An implementation of filterMap which can be applied uniformly to things which are, or which are
// convertible to, a GenTraversable (ie. standard collections, but also Arrays and Strings).
//
// The result type of this implementation of filterMap will be the most precise possible, in the
// same way that you would expect for plain map on the same receiver.
//
// The ElementType stuff is a workaround for deficiencies in Scala's type inference, and it
// requires -Ydependent-method-types or scala 2.10-SNAPSHOT.
import scala.collection.GenTraversable
import scala.collection.generic.CanBuildFrom
import scala.collection.mutable.Builder
class FilterMap[CC, A](cc : CC)(implicit ev : CC => GenTraversable[A]) {
def filterMap[B, That](f : A => Option[B])(implicit cbf : CanBuildFrom[CC, B, That]) : That = {
val builder: Builder[B, That] = cbf()
cc foreach { x => val y = f(x); if (y.nonEmpty) builder += y.get }
builder.result
}
}
trait ElementType[CC] {
type E
val ev : CC => GenTraversable[E]
}
implicit def elementType[A, CC[_]](implicit ev0 : CC[A] => GenTraversable[A]) = new ElementType[CC[A]] {
type E = A
val ev = ev0
}
implicit val stringElementType = new ElementType[String] {
type E = Char
val ev = implicitly[String => GenTraversable[Char]]
}
implicit def filterMap[CC, A](c : CC)(implicit et : ElementType[CC]) = new FilterMap[CC, et.E](c)(et.ev)
val l = List(1, 2, 3, 4, 5)
val a = Array(1, 2, 3, 4, 5)
val s = "Hello World"
def even(i : Int) = if(i % 2 == 0) Some(i) else None
def evenc(c : Char) = if(c % 2 == 0) Some(c.toInt) else None
def upper(c : Char) = if(c >= 'A' && c <= 'Z') Some(c) else None
val fml = l.filterMap(even) // type is List[Int]
assert(fml == List(2, 4))
val fma = a.filterMap(even) // type is Array[Int]
fma : Array[Int]
assert(fma sameElements Array(2, 4))
val fms1 = s.filterMap(upper) // type is String
assert(fms1 == "HW")
val fms2 = s.filterMap(evenc) // type is IndexedSeq[Int]
assert(fms2 == IndexedSeq(72, 108, 108, 32, 114, 108, 100))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment