Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flatMap(func) versus flatMap(func(_))

Tags:

scala

I'm very surprised by not being able to find an existing question for that one. Why is that, given:

val p: Int => Option[Int] = Some(_)
List(1, 2, 3).flatMap(p)

I'm getting:

<console>:14: error: type mismatch;
 found   : Int => Option[Int]
 required: Int => scala.collection.GenTraversableOnce[?]
       List(1, 2, 3).flatMap(p)

But if I replace last line with this one, it compiles and works as expected:

List(1, 2, 3).flatMap(p(_))

My take on the problem is that in case of p(_) the type inference system kicks in to decide on the type of lambda, and on the way it finds appropriate implicit conversion for Option[Int] (option2Iterable, I believe). With just p, the type is already known, and it's incorrect, so no conversion is attempted (and there's no conversion for Function1 returning Option to Function1 returning GenTraversableOnce).

Is this reasoning right? And if so, is there some reason why I shouldn't report this as a bug/issue?

EDIT: A new twist: I've seen p.apply mentioned in some (sadly) deleted comment (though this was about coding style). Surprisingly, it works just as well as p(_) does.

like image 258
TNW Avatar asked Jun 09 '16 12:06

TNW


People also ask

What does flatMap () in Spark do?

Spark flatMap() transformation flattens the DataFrame column after applying the function on every element and returns a new DataFrame respectively. The returned DataFrame can have the same count or more elements than the current DataFrame.

What is the difference between map and flatMap transformation in Spark streaming?

Both map() and flatMap() are used for transformations. The map() transformation takes in a function and applies it to each element in the RDD and the result of the function is a new value of each element in the resulting RDD. The flatMap() is used to produce multiple output elements for each input element.

What is difference between map and flatMap in Scala?

The flatMap() method is similar 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. The flatMap method acts as a shorthand to map a collection and then immediately flatten it.

What is flatMap?

The flatMap() method returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level. It is identical to a map() followed by a flat() of depth 1 ( arr.map(...args).flat() ), but slightly more efficient than calling those two methods separately.


2 Answers

When you type List(1, 2, 3).flatMap(p(_)) what's done behind the scenes is that function p gets spawned and wrapped in another function that partially applies it - meaning all necessary implicit conversions, if any, will also get applied inside the body of this new function.

When you type List(1, 2, 3).flatMap(p), no function application happens, and you try to pass an Int => Option[Int] which is incompatible with the signature Int => GenTraversableOnce[Int], and although the scope contains an implicit conversion from Option[T] to Iterable[T], there's no conversion from Function1[Int, Option[Int]] to Function1[Int, Iterable[Int]] defined.

The reason for that, probably, is because functions of arbitrary arity have virtually infinite amount of variations due to generics, and since Functions do not share a supertrait, that would require quite a bunch of implicits for each type of functions.


Here's a construct that extends flatMap just enough to achieve the desired result for p. However, it makes the already obscure signature of flatMap even less clear (much less clear). I believe, there's no technical block from implementing this behavior, but complexity of signatures is the reason why scala-collections library is often hailed.

import scala.collection.GenTraversableOnce
import scala.collection.generic.CanBuildFrom

implicit class ListEx[A](list: List[A]) {
  def flatMap2[B, M[_], That](f: A => M[B])
                             (implicit bf: CanBuildFrom[List[A], B, That],
                                       view: M[B] => GenTraversableOnce[B]): That =
    list.flatMap(f andThen view)
}

val p: Int => Option[Int] = Some(_)

List(1, 2, 3) flatMap2 p
like image 137
Sergey Avatar answered Oct 24 '22 19:10

Sergey


List(1, 2, 3).flatMap(p(_))

is compiled to:

List(1,2,3).flatMap(x => p(x))

And as p(x) is returning Option[Int] and flatMap needs GenTraversableOnce[Int] so scala.Option.option2Iterable is applied.

Option does not inherit from GenTraversableOnce. To make this syntax work:

List(1,2,3).flatMap(p)

you need implicit convertion from Int => Option[Int] to Int => GenTraversableOnce[Int], sth like this:

import scala.collection.GenTraversableOnce

implicit def conv(c: Int => Option[Int]): Int => GenTraversableOnce[Int] = {
    a => Option.option2Iterable(c(a))
}
val p: Int => Option[Int] = Some(_)
List(1, 2, 3).flatMap(p)

For me this is not a bug, but I agree, it's not intuitive either.

like image 41
liosedhel Avatar answered Oct 24 '22 19:10

liosedhel