4

Why is the below statement valid for .map() but not for .flatMap()?

 val tupled = input.map(x => (x*2, x*3))

 //Compilation error: cannot resolve reference flatMap with such signature
 val tupled = input.flatMap(x => (x*2, x*3))

This statement has no problem, though:

val tupled = input.flatMap(x => List(x*2, x*3))
Nathaniel Ford
  • 20,545
  • 20
  • 91
  • 102
Somasundaram Sekar
  • 5,244
  • 6
  • 43
  • 85
  • What is type of `input`? Check `map` and `flatMap` method signatures. Its have different parameters that's why your code has compile error. – Sergii Lagutin Aug 12 '16 at 18:40
  • Because `flatMap` is used for flattening structure. When you get a `M[M[A]]` with `map` you will get `M[A]` with `flatMap`. – insan-e Aug 12 '16 at 18:40
  • The nested `M` can sometimes be different, `N`.. For example `List[Option[String]]` => `List[String]` – insan-e Aug 12 '16 at 18:42
  • the thing is `a.flatMap(a => b)` can be roughly approximated by `a.map(a => b).flatten`. Basically for `flatMap` to work, the return type of function should be a monad or something which you can flatten. – sarveshseri Aug 12 '16 at 18:45

2 Answers2

5

Assuming input if of type List[Int], map takes a function from Int to A, whereas flatMap takes a function from Int to List[A].

Depending on your use case you can choose either one or the other, but they're definitely not interchangeable.

For instance, if you are merely transforming the elements of a List you typically want to use map:

List(1, 2, 3).map(x => x * 2) // List(2, 4, 6)

but you want to change the structure of the List and - for example - "explode" each element into another list then flattening them, flatMap is your friend:

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

Using map you would have had List(List(1), List(2, 2), List(3, 3, 3)) instead.

Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
4

To understand how this is working, it can be useful to explicitly unpack the functions you're sending to map and flatMap and examine their signatures. I've rewritten them here, so you can see that f is a function mapping from Int to an (Int, Int) tuple, and g is a function that maps from Int to a List[Int].

val f: (Int) => (Int, Int) = x => (x*2, x*3)
val g: (Int) => List[Int] = x => List(x*2, x*3)

List(1,2,3).map(f)
//res0: List[(Int, Int)] = List((2,3), (4,6), (6,9))
List(1,2,3).map(g)
//res1: List[List[Int]] = List(List(2, 3), List(4, 6), List(6, 9))
//List(1,2,3).flatMap(f)  // This won't compile
List(1,2,3).flatMap(g)
//res2: List[Int] = List(2, 3, 4, 6, 6, 9)

So why won't flatMap(f) compile? Let's look at the signature for flatMap, in this case pulled from the List implementation:

final override def flatMap[B, That](f : scala.Function1[A, scala.collection.GenTraversableOnce[B]])(...)

This is a little difficult to unpack, and I've elided some of it, but the key is the GenTraversableOnce type. List, if you follow it's chain of inheritance up, has this as a trait it is built with, and thus a function that maps from some type A to a List (or any object with the GenTraversableOnce trait) will be a valid function. Notably, tuples do not have this trait.

That is the in-the-weeds explanation why the typing is wrong, and is worth explaining because any error that says 'cannot resolve reference with such a signature' means that it can't find a function that takes the explicit type you're offering. Types are very often inferred in Scala, and so you're well-served to make sure that the type you're giving is the type expected by the method you're calling.

Note that flatMap has a standard meaning in functional programming, which is, roughly speaking, any mapping function that consumes a single element and produces n elements, but your final result is the concatenation of all of those lists. Therefore, the function you pass to flatMap will always expect to produce a list, and no flatMap function would be expected to know how to act on single elements.

Graham
  • 7,431
  • 18
  • 59
  • 84
Nathaniel Ford
  • 20,545
  • 20
  • 91
  • 102