Map(...).flatten != flatMap ?!


#1
val xs = Seq(1,2,-1,3)
val f: Int => Option[Int] =  x => if(x >= 0) Some(x) else None

def filter1(f: Int => Option[Int]) = xs.map(f).flatten
def filter2(f: Int => Option[Int]) = xs.flatMap(f)

error: type mismatch;
 found   : scala.this.Function1[scala.this.Int,scala.this.Option[scala.this.Int]]
 required: scala.this.Function1[scala.this.Int,collection.this.GenTraversableOnce[?]]
  def filter2(f: Int => Option[Int]) = xs.flatMap(f)
                                                  ^

filter1 works but is inefficient since it creates an intermediate Seq.
filter2 doesn’t compile.

Is there an efficient workaround?


#2

This’ll probably work:

def filter2(f: Int => Option[Int]) = xs.flatMap(f andThen implicitly[Option[Int] => TraversableOnce[Int]])

#3

To understand why this is happening, I suggest reading this explanation of your problem.

In this example, the results should be equivalent:

val xs = Seq(1,2,-1,3)
def f(x: Int) = Option(x).filter(_ >= 0)

xs.map(f).flatten
xs.flatMap(f)

#4

Thanks for the suggestions.
I might have simplified a little too much. My actual use case looks (with a more complex merge method) more like:

case class Foo(x: Option[String], y: Option[Int])
val foos = Seq(Foo(Some("abc"), None), 
               Foo(None, Some(42))
               )

def merge[T](f: Foo => Option[T]): Option[T] = foos.map(f).flatten.headOption

val x = merge(_.x)
val y = merge(_.y)
val result = Foo(x,y)

So I’d rather keep the filter away from the use site where the function is defined.

The implicit thing is clever. I’m not sure where the actual implicit conversion is defined (it’s not in Predef) so I can’t take a look, but are

f andThen implicitly[Option[T] => TraversableOnce[T]]

and

f.andThen(_.toList)

of equivalent efficiency?


#5

Implicit lookup for conversions also looks in the companion object of the source type, so implicitly[Option[T] => TraversableOnce[T]] should probably return option2iterable from Option’s companion, the implementation of which is just calling toList.


#6
def filter3(f: Int => Option[Int]) =
  for {x <- xs
       y <- f(x)
  } yield y

might work