Search code examples
haskellreader-monad

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

Solution

  • 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