Some people will find that using a function called map to convert a Future[Response] into a Future[Result] may seem a little strange. Let’s try to build some intuition around it.
map is actually a functional programming concept that's easiest to understand when thinking about Lists. If you call map on a List, you get back a new List where each element of the new List is the result of applying a function to each element of the originalList. For example, consider this lower function:
What happens if we map this function over a List of Strings?
We get back a new List of Strings that are all lower case. In other words, we transformed a List[String] into a new List[String].
It’s also possible to use map to convert a List[X] into a List of some other type, such as a List[Y]. For example, consider this strlen function:
What happens if we map this function over the same List of Strings?
We now get back a new List, where each element represents the length of a String in the original List. That is, we’ve now transformed a List[String] into a new List[Int].
Let’s look at one more case: a function called explode that returns a List of all the characters in the given String:
Look at what happens if we map our List of Strings over explode:
We’ve now transformed a List[String] into a new List[List[Char]]. What if we didn’t want to have the nested List-in-a-List? To do that, we can use flatMap instead of map:
As the name implies, flatMap behaves just like map, except it combines (flattens) the nested Lists into a single, flat List.
The map and flatMap functions aren’t unique to Lists. They are just as useful for other types of "containers", such as Array, Set, Map, and so on:
As it turns out, a Future[T] is also a "container": it’s a bit of a strange one, in that it contains just one element, but the same map and flatMap functions can be used to transform a Future[X] into a Future[Y]:
One detail that we’ve glossed over is that the "container" class controls when it actually calls the functions passed into map and flatMap. A List may call the function immediately. However, a Future will only do it when the I/O has completed and data is available.
This is ok, because the return value for Future.map and Future.flatMap is anotherFuture. You can immediately transform, compose, and reason about this returned value because it also will control when it calls any functions passed to its map, flatMap, and other methods.