3.10. Monadic Operations
When you work with generic types, and with functions that yield values from these types, it is useful to supply methods that let you compose these functions—that is, carry out one after another. In this section, you will see a design pattern for providing such compositions.
Consider a generic type G<T> with one type parameter, such as List<T> (zero or more values of type T), Optional<T> (zero or one values of type T), or Future<T> (a value of type T that will be available in the future).
Also consider a function T -> U, or a Function<T, U> object.
It often makes sense to apply this function to a G<T> (that is, a List<T>, Optional<T>, Future<T>, and so on). How this works exactly depends on the nature of the generic type G. For example, applying a function f to a List with elements e1,..., en means creating a list with elements f(e1),..., f(en).
Applying f to an Optional<T> containing v means creating an Optional<U> containing f(v). But if f is applied to an empty Optional<T> without a value, the result is an empty Optional<U>.
Applying f to a Future<T> simply means to apply it whenever it is available. The result is a Future<U>.
By tradition, this operation is usually called map. There is a map method for Stream and Optional. The CompletableFuture class that we will discuss in Chapter 6 has an operation that does just what map should do, but it is called thenApply. There is no map for a plain Future<V>, but it is not hard to supply one (see Exercise 21).
So far, that is a fairly straightforward idea. It gets more complex when you look at functions T -> G<U> instead of functions T -> U. For example, consider getting the web page for a URL. Since it takes some time to fetch the page, that is a function URL -> Future<String>. Now suppose you have a Future<URL>, a URL that will arrive sometime. Clearly it makes sense to map the function to that Future. Wait for the URL to arrive, then feed it to the function and wait for the string to arrive. This operation has traditionally been called flatMap.
The name flatMap comes from sets. Suppose you have a “many-valued” function—a function computing a set of possible answers. And then you have another such function. How can you compose these functions? If f(x) is the set {y1,..., yn}, you apply g to each element, yielding {g(y1),..., g(yn)}. But each of the g(yi) is a set. You want to “flatten” the set of sets so that you get the set of all possible values of both functions.
There is a flatMap for Optional<T> as well. Given a function T -> Optional<U>, flatMap unwraps the value in the Optional and applies the function, except if either the source or target option was not present. It does exactly what the set-based flatMap would have done on sets with size 0 or 1.
Generally, when you design a type G<T> and a function T -> U, think whether it makes sense to define a map that yields a G<U>. Then, generalize to functions T -> G<U> and, if appropriate, provide flatMap.