2

I have been trying to get to grips with the reader monad and came across this tutorial. In it, the author presents this example:

example2 :: String -> String
example2 context = runReader (greet "James" >>= end) context
    where
        greet :: String -> Reader String String
        greet name = do
            greeting <- ask
            return $ greeting ++ ", " ++ name

        end :: String -> Reader String String
        end input = do
            isHello <- asks (== "Hello")
            return $ input ++ if isHello then "!" else "."

I know that this is a trivial example that shows the mechanics, but I am trying to figure out why it would be better than doing something like:

example3 :: String -> String
example3 = end <*> (greet "James")
  where
    greet name input = input ++ ", " ++ name
    end   input      = if input == "Hello" then (++ "!") else (++ ".")
duplode
  • 33,731
  • 7
  • 79
  • 150
matt
  • 1,817
  • 14
  • 35
  • Not everything you find on the internet is better than everything you write yourself. – dfeuer Mar 26 '18 at 16:11
  • I don't think that the example is meant to show the usefulness of the reader monad (which is not that huge), but to show how to use `ask, asks`. – chi Mar 26 '18 at 16:14
  • @dfeuer so do I assume that trying write a function the way I did would be a better option. – matt Mar 26 '18 at 16:21
  • @chi is the reader monad really not that useful? I keep hearing that it is .. :( – matt Mar 26 '18 at 16:21
  • 2
    @matthias, the reader monad *transformer* is much more useful. In your version, you should either drop the `<*>` business or eta reduce `context` argument away. – dfeuer Mar 26 '18 at 16:25
  • It is something that should be learned -- you are not wasting your time. It is however rare (IMO) to see the reader monad being used on its own. Usually, the reader transformer is used (as dfeuer mentions), or some other transformer instantiated with the reader monad. – chi Mar 26 '18 at 16:31
  • 1
    There are cases where Reader (or the monad instance for ((->) r)) is useful in the real world - but those cases are places where you have code that's polymorphic in a type constructor and your use case is passing an argument in. This can come up when using the polymorphic types offered by lenses, for instance. – Carl Mar 26 '18 at 18:31

2 Answers2

9

Reader isn't often used by itself in real code. As you have observed, it's not really better than just passing an extra argument to your functions. However, as part of a monad transformer it is an excellent way to pass configuration parameters through your application. Usually this is done by adding a MonadReader constraint to any function that needs access to configuration.

Here's an attempt at a more real-world example:

data Config = Config
  { databaseConnection :: Connection
  , ... other configuration stuff
  }

getUser :: (MonadReader Config m, MonadIO m) => UserKey -> m User
getUser x = do
   db <- asks databaseConnection
   .... fetch user from database using the connection

then your main would look something like:

main :: IO ()
main = do
  config <- .... create the configuration
  user <- runReaderT (getUser (UserKey 42)) config
  print user
user2297560
  • 2,953
  • 1
  • 14
  • 11
1

dfeuer, chi and user2297560 are right in that "Reader isn't often used by itself in real code". It is worth noting, though, that there is next to no essential difference between what you do in the second snippet in the question and actually using Reader as a monad: the function functor is just Reader without the wrappers, and the Monad and Applicative instances for both of them are equivalent. By the way, outside of highly polymorphic code1, the typical motivation for using the function Applicative is making code more pointfree. In that case, moderation is highly advisable. For instance, as far as my own taste goes, this...

(&&) <$> isFoo <*> isBar

... is fine (and sometimes it might even read nicer than the pointful spelling), while this...

end <*> greet "James"

... is just confusing.


Footnotes

  1. For instance, as Carl points out in a comment, it and the related instances can be useful in...

    [...] places where you have code that's polymorphic in a type constructor and your use case is passing an argument in. This can come up when using the polymorphic types offered by lenses, for instance.

Community
  • 1
  • 1
duplode
  • 33,731
  • 7
  • 79
  • 150
  • perhaps `end <*> greet "James"` is confusing because of the function names ... maybe something like `addEnding <*> great "James"` would be less so? – matt Mar 26 '18 at 21:51
  • [1/2] @matthias It is better, but only a little, I'd say. The matter with `(<*>)` for functions is that (1) behind its compact looks, there is quite a bit of stuff going on for something that only involves function application (`g <*> f = \x -> g x (f x)`); and (2) uses of the function `Applicative` instance tend to catch people by surprise. – duplode Mar 26 '18 at 22:12
  • [2/2] @matthias It's all a bit subjective, but those issues are mitigated in `(&&) <$> isFoo <*> isBar` (or `liftA2 (&&) isFoo isBar`, which I like slightly less in the specific case of functions) by the familiar shape of the applicative style expression, as well as by `isFoo` and `isBar` clearly looking like single-argument functions, and `(&&)` transparently being a two-argument function -- in contrast, the arity of `greet "James"` is not immediately obvious. Those factors make it easier to recognise that the function `Applicative` is being used. – duplode Mar 26 '18 at 22:12
  • thanks, but can you please give me an example of how you would write it your self? – matt Mar 26 '18 at 22:24
  • @matthias In the plain old way: `\input -> addEnding input (greet "James" input)`. While I like taking up opportunities for omitting named arguments, I don't feel using `(<*>)` for that pays off in this case. – duplode Mar 26 '18 at 22:27