matt
matt

Reputation: 2039

Reader Monad - explanation of trivial case

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 (++ ".")

Upvotes: 2

Views: 1585

Answers (2)

duplode
duplode

Reputation: 34378

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.

Upvotes: 1

user2297560
user2297560

Reputation: 2983

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

Upvotes: 9

Related Questions