7

Having read http://learnyouahaskell.com/functors-applicative-functors-and-monoids#applicative-functors , I can provide an example of the use of functions as applicative functors:

Let's say res is a function of 4 arguments and fa, fb, fc, fd are all functions that take a single argument. Then, if I'm not mistaken, this applicaive expression:

f <$> fa <*> fb <*> fc <*> fd $ x

Means the same as this non-fancy expression:

f (fa x) (fb x) (fc x) (fd x)

Ugh. Took me quite a bit of time to understand why this is the case, but - with the help of a sheet of paper with my notes - I should be able to prove this.

Then I read http://learnyouahaskell.com/for-a-few-monads-more#reader . And we're back at this stuff again, this time in the monadic syntax:

do
    a <- fa
    b <- fb
    c <- fc
    d <- fd
    return (f a b c d)

While another A4 sheet of notes was needed for me to prove this, I'm now pretty confident that this, again, means the same:

    f (fa x) (fb x) (fc x) (fd x)

I'm confused. Why? What's the use of this?

Or, to be more precise: This seems to me to just duplicate the functionality of functions as applicatives, but with a more verbose syntax.

So, could you give me an example of can the Reader monad do that functions as applicatives cannot?

Actually, I would also like to ask what's the use of any of these two: applicative functions OR the Reader monad - because while being able to apply the same argument to four functions (fa, fb, fc, fd) without repeating this argument four times does reduce some repetitiveness, I'm not sure if this minute improvement justifies this level of complexity; so I must be missing something prominent, I think; but this is worthy of a separate question

  • 2
    No, it is not equivalent to f (fa x) (fb x) (fc x) (fd x). The do notation is syntactical sugar, it means that you wrote fa >>= \a -> fb >>= \b -> fc >>= \c -> fd >>= \d -> return (f a b c d). Note that the >>= depending on the monad does something different. – Willem Van Onsem Apr 22 at 16:14
  • 4
    @WillemVanOnsem Yes, I know that the do notation is synctatical sugar to what you wrote; however, having filled an A4 sheet of paper, I'm reasonably sure that IN THE CASE OF THE READER MONAD (not any monad) fa >>= \a -> fb >>= \b -> fc >>= \c -> fd >>= \d -> return (f a b c d) actually is equivalent to f (fa x) (fb x) (fc x) (fd x) – gaazkam Apr 22 at 16:15
  • It's a fair question, but you could ask the same about many monads - some of what you can do with a monad can also be done with an applicative. But quite a bit can't (sadly I don't have a good example off the top of my head with Reader, but I'm sure someone more expert than me will), and it's unfortunate that LYAH chooses an example which could be done in applicative notation instead. – Robin Zigmond Apr 22 at 16:17
  • The Reader a monad and the (->) a monad are isomorphic. The difference between them is only a wrapping constructor. You can choose either according to "taste", so to speak. I guess Reader a is more used since it has a name, and the wrapping helps understanding what is thought as monadic and what is a regular function. – chi Apr 22 at 16:18
  • 4
    While in general Monad is more powerful than Applicative (as explained in chepner's answer), the Reader/function functor is a very special case in which the Monad and Applicative instances happen to be equivalent -- see the second part of this answer of mine. (Cc. @RobinZigmond ) – duplode Apr 22 at 16:39
8

The monadic version lets you add additional logic between the calls to the functions found in the context, or even decide not to call them at all.

do
    a <- fa
    if a == 3 
      then  return (f a 1 1 1)
      else  do
          b <- fb
          c <- fc
          d <- fd
          return (f a b c d)

In your original do expression, it's true that you aren't doing anything that the Applicative instance couldn't do, and in fact, the compiler can determine that. If you use the ApplicativeDo extension, then

do
    a <- fa
    b <- fb
    c <- fc
    d <- fd
    return (f a b c d)

would indeed desugar to f <$> fa <*> fb <*> fc <*> fd instead of fa >>= \a -> fb >>= \b -> fc >>= \c -> fd >>= \d -> return (f a b c d).


This all holds for other types as well, for example

  • Maybe:

    f <$> (Just 3) <*> (Just 5)
      == Just (f 3 5)
      == do
          x <- Just 3
          y <- Just 5
          return (f 3 5)
    
  • []:

    f <$> [1,2] <*> [3,4]
      == [f 1 3, f 1 4, f 2 3, f 2 4]
      == do
          x <- [1,2]
          y <- [3,4]
          return (f x y)
    
  • 2
    It is worth noting that Reader is a special case in which the instances are actually equivalent. – duplode Apr 22 at 16:41
  • This does a good job at explaining why the do notation is more useful than the applicative notation, but it doesn't touch on why the applicative notation is more useful than the "non-fancy" notation. – Joseph Sible Apr 22 at 23:45
  • I didn't have anything more terribly intelligent (or less punny) to say on the matter other than "it's terser when applicable". – chepner Apr 23 at 0:22
4

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...

do
    x <- fa
    y <- fb
    return (g x y)

... switching from Applicative to 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):

do
    x <- fa
    y <- if x >= 0 then fb else fc
    return (g x y)

While 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 Applicative and 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.


Still, with 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 Data.Char:

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 ReaderT over 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 join from Control.Monad does for the function functor, and then work out why that makes sense. (A solution can be found in this Q&A.)

  • 2
    @FabianSchneider I rewrote your edit because I feel that, as far as the flow of the text goes, part of the answer isn't very well suited for a long-ish digression of that sort. The idea of adding a more concrete illustration there was good; thanks for that. (On another note: applicative expressions can have effects, or even side effects if we're talking about IO-like things. I'd rather describe the key difference in terms of monads allowing effects to depend on results of computations.) – duplode Apr 23 at 21:47
  • Seems reasonable; true, they can have effects; i should have mentioned that too in the longer version of the text :) That is indeed a very nice description of the difference. – Fabian Schneider Apr 24 at 7:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.