12
scala> List(List(1), List(2), List(3), List(4))
res18: List[List[Int]] = List(List(1), List(2), List(3), List(4))

scala> res18.flatten
res19: List[Int] = List(1, 2, 3, 4)

scala> res18.flatMap(identity)
res20: List[Int] = List(1, 2, 3, 4)

Is there any difference between these two functions? When is it appropriate to use one over the other? Are there any tradeoffs?

Ram Ghadiyaram
  • 28,239
  • 13
  • 95
  • 121
user3335040
  • 649
  • 1
  • 7
  • 17

3 Answers3

15

You can view flatMap(identity) as map(identity).flatten. (Of course it is not implemented that way, since it would take two iterations).

map(identity) gives you the same collection, so in the end it is the same as only flatten.

I would personally stick to flatten, since it is shorter/easier to understand and designed to exactly do this.

Kigyo
  • 5,668
  • 1
  • 20
  • 24
6

Conceptually there is no difference in the result... flatMap is taking bit more time to produce same result...

I will show it more practically with an example of flatMap, map & then flatten and flatten

object Test extends App {
  // flatmap
  println(timeElapsed(List(List(1, 2, 3, 4), List(5, 6, 7, 8)).flatMap(identity)))
  // map and then flatten
  println(timeElapsed(List(List(1, 2, 3, 4), List(5, 6, 7, 8)).map(identity).flatten))
  // flatten
  println(timeElapsed(List(List(1, 2, 3, 4), List(5, 6, 7, 8)).flatten))

  /**
   * timeElapsed
   */
  def timeElapsed[T](block: => T): T = {
    val start = System.nanoTime()
    val res = block
    val totalTime = System.nanoTime - start
    println("Elapsed time: %1d nano seconds".format(totalTime))
    res
  }
}


Both flatMap and flatten executed with same result after repeating several times

Conclusion : flatten is efficient

Elapsed time: 2915949 nano seconds
List(1, 2, 3, 4, 5, 6, 7, 8)
Elapsed time: 1060826 nano seconds
List(1, 2, 3, 4, 5, 6, 7, 8)
Elapsed time: 81172 nano seconds
List(1, 2, 3, 4, 5, 6, 7, 8)
Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Ram Ghadiyaram
  • 28,239
  • 13
  • 95
  • 121
3

Conceptually, there is no difference. Practically, flatten is more efficient, and conveys a clearer intent.

Generally, you don't use identity directly. It's more there for situations like it getting passed in as a parameter, or being set as a default. It's possible for the compiler to optimize it out, but you're risking a superfluous function call for every element.

You would use flatMap when you need to do a map (with a function other than identity) immediately followed by a flatten.

Karl Bielefeldt
  • 47,314
  • 10
  • 60
  • 94
  • The last sentence ("You would use..") is misleading, `flatmap` can't always be replace with `map` + `flatten` and it normally conveys a different computation – Chirlo Aug 14 '17 at 10:43
  • @Chirlo, I think you are mistaken there, but I'm willing to admit if I am wrong. Can you please provide a counterexample? – Karl Bielefeldt Aug 14 '17 at 11:04
  • with `map` + `flatten` you'll always get a `List` with the same number of elements as the original. With `flatmap` you could can do more manipulations, like `list.flatmap( x => if (x > 2 ) List(x,x,x,x) else List())`. This would yield ( from the OPs example) : `List(3,3,3,3,4,4,4,4)`. You can implement `filter`, `takeWhile`.... in terms of `flatMap`, which you can't do with `map+flatten` – Chirlo Aug 14 '17 at 11:09
  • Your first example with `map+flatten` is `list.map(x=>if (x>2) List(x,x,x,x) else List()).flatten`. `filter` is `list.map(x => if (pred(x)) List(x) else List()).flatten`. – Karl Bielefeldt Aug 14 '17 at 11:22
  • Oh, ok, I missunderstood the example, but you can get [into situations](https://stackoverflow.com/questions/20215518/scala-map-flatten-and-flatmap-not-equivalent) where both are not equivalent, because of scala's signature of `flatmap` (with `CanBuildFrom`). – Chirlo Aug 14 '17 at 15:33