Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Behavior of flatMap when applied to List[Option[T]]

Let's take a look at this code:

scala> val a = List(Some(4), None)
a: List[Option[Int]] = List(Some(4), None)
scala> a.flatMap( e=> e)
List[Int] = List(4)

Why would applying flatMap with the function { e => e } on a List[Option[T]] returns a List[T] with the None elements removed?

Specifically, what is the conceptual reasoning behind it -- is it based on some existing theory in functional programming? Is this behavior common in other functional languages?

This, while indeed useful, does feel a bit magical and arbitrary at the same time.

EDIT:

Thank you for your feedbacks and answer. I have rewritten my question to put more emphasis on the conceptual nature of the question. Rather than the Scala specific implementation details, I'm more interested in knowing the formal concepts behind it.

like image 759
lolski Avatar asked Mar 23 '15 15:03

lolski


People also ask

When to use map and flatMap in Scala?

In Scala, flatMap() method is identical to the map() method, but the only difference is that in flatMap the inner grouping of an item is removed and a sequence is generated. It can be defined as a blend of map method and flatten method.

Why do we need flatMap?

We can use a flatMap() method on a stream with the mapper function List::stream. On executing the stream terminal operation, each element of flatMap() provides a separate stream. In the final phase, the flatMap() method transforms all the streams into a new stream.

What is the use of flatMap in Scala?

flatMap in Scala are used to generate a separate set of sequence it is used with collection data structures and we can also use them with mutable and immutable collection objects. It also helps in faster processing of collection elements by making them parallel.

Does flatMap preserve order Scala?

Yes, flatMap keeps the order intact.


2 Answers

I assume you mean the support for mapping and filtering at the same time with flatMap:

scala> List(1, 2).flatMap {
     |   case i if i % 2 == 0 => Some(i)
     |   case i => None
     | }
res0: List[Int] = List(2)

This works because Option's companion object includes an implicit conversion from Option[A] to Iterable[A], which is a GenTraversableOnce[A], which is what flatMap expects as the return type for its argument function.

It's a convenient idiom, but it doesn't really exist in other functional languages (at least the ones I'm familiar with), since it relies on Scala's weird mix of subtyping, implicit conversions, etc. Haskell for example provides similar functionality through mapMaybe for lists, though.

like image 193
Travis Brown Avatar answered Sep 27 '22 20:09

Travis Brown


Let's first look at the Scaladoc for Option's companion object. There we see an implicit conversion:

implicit def option2Iterable[A](xo: Option[A]): Iterable[A]

This means that any option can be implicitly converted to an Iterable, resulting in a collection with zero or one elements. If you have an Option[A] where you need an Iterable[A], the compiler will add the conversion for you.

In your example:

val a = List(Some(4), None)
a.flatMap(e => e)

We are calling List.flatMap, which takes a function A => GenTraversableOnce[B]. In this case, A is Option[Int] and B will be inferred as Int, because through the magic of implicit conversion, e when returned in that function will be converted from an Option[Int] to an Iterable[Int] (which is a subtype of GenTraversableOnce).

At this point, we've essentially done the following:

List(List(1), Nil).flatMap(e => e)

Or, to make our implicit explicit:

List(Option(1), None).flatMap(e => e.toList)

flatMap then works on Option as it does for any linear collection in Scala: take a function of A => List[B] (again, simplifying) and produce a flattened collection of List[B], un-nesting the nested collections in the process.

like image 21
Ryan Avatar answered Sep 27 '22 20:09

Ryan