Before getting to your main question about
Reader, I will start with a few remarks about applicative-versus-monad in general. While this applicative style expression...
g <$> fa <*> fb
... is indeed equivalent to this do-block...
x <- fa
y <- fb
return (g x y)
... switching from
Monad makes it possible to make decisions about which computations to perform based on results of other computations, or, in other words, to have effects that depend on previous results (see also chepner's answer):
x <- fa
y <- if x >= 0 then fb else fc
return (g x y)
Monad is more powerful than
Applicative, I suggest not thinking of it as if one were more useful than the other. Firstly, because there are applicative functors that aren't monads; secondly, because not using more power than you actually need tends to make things simpler overall. (In addition, such simplicity can sometimes bring tangible benefits, such as an easier time dealing with concurrency.)
A parenthetical note: when it comes to applicative-versus-monad,
Reader is a special case, in that the
Monad instances happen to be equivalent. For the function functor (that is,
((->) r), which is
Reader r without the newtype wrapper), we have
m >>= f = flip f <*> m. That means if take the second do-block I wrote just above (or the analogous one in chepner's answer, etc) and assume the monad being used is
Reader, we can translate it into applicative style.
Reader ultimately being such a simple thing, why should we even bother with any of the above in this specific case? Here go a few suggestions.
To begin with, Haskellers are often wary of the bare function functor,
((->) r), and quite understandably so: it can easily lead to unnecessarily cryptic code when compared to "non-fancy expression[s]" in which functions are applied directly. Still, in a few select cases it can be handy to use. For a tiny example, consider these two functions from
isUpper :: Char -> Bool
isDigit :: Char -> Bool
Now let's say we want to write a function that checks if a character is either an upper case letter or an ASCII digit. The straightforward thing to do is something along the lines of:
\c -> isUpper c && isDigit c
Using the applicative style, though, we can write it immediately in terms of the two functions -- or, I'm inclined to say, the two properties -- without having to note where the eventual argument goes:
(&&) <$> isUpper <*> isDigit
With an example as tiny as this one, whether to write it in this way is not a big deal, and largely up to taste -- I quite like it; others can't stand it. The point, though, is that sometimes we aren't particularly concerned about a certain value being a function, because we happen to be thinking of it as something else -- in this case, as a property -- and the fact it is ultimately a function can appear to us as a mere implementation detail.
A quite compelling example of this perspective shift involves application-wide configuration parameters: if every single function across some layer of your program takes some
Config value as an argument, chances are you will find it more comfortable treating its availability as a background assumption, rather than passing it around explicitly everywhere. It turns out that is the main use case for the reader monad.
In any case, your suspicions about the usefulness of
Reader are somewhat vindicated in at least one manner. It turns out that
Reader itself, the functions-but-wrapped-in-a-fancy-newtype functor, isn't actually used all that often in the wild. What is extremely common are monadic stacks that incorporate the functionality of
Reader, typically through the means of
ReaderT and/or the
MonadReader class. Discussing monad transformers at length would be a digression too far for the space of this answer, so I will just note that you can work with, for example,
ReaderT r IO much like you would with
Reader r, except that you can also slip in
IO computations along the way. It is not unusual to see some variant of
IO as the core type of the outer layer of a Haskell application.
On a final note, you might find it interesting to see what
Control.Monad does for the function functor, and then work out why that makes sense. (A solution can be found in this Q&A.)