Modular Program Design - Combining Monad Transformers in Monad Agnostic functions

haskell, monad-transformers

Solution

Your `mainProgram` signature is problematic, because the `MonadReader` typeclass contains the functional dependency `MonadReader r m | m -> r`. This essentially means that a single concrete type cannot have a `MonadReader` instance for multiple different types. So when you say that the type `m` has both instances `MonadReader MessageCase` and `MonadReader LogConfig` it goes against the dependency declaration.

The easiest solution is to change `mainProgram` to have a non-generic type:

mainProgram :: ReaderT MessageCase (ReaderT LogConfig IO) ()
mainProgram = do input <- getUserInput
                 lift $ doLog input
                 liftIO $ putStrLn $ "Entry logged: " ++ input

This also requires the explicit `lift` for `doLog`.

Now you can run the `mainProgram` by running each `ReaderT` separately, like this:

main :: IO ()
main = do
    let messageCase = undefined :: MessageCase
        logConfig   = undefined :: LogConfig
    runReaderT (runReaderT mainProgram messageCase) logConfig

If you want to have a generic function that uses two different `MonadReader` instances, you need to make it explicit in the signature that one reader is a monad transformer on top of the other reader.

mainProgram :: (MonadTrans mt, MonadReader MessageCase (mt m), MonadReader LogConfig m, MonadIO (mt m), MonadIO m) => mt m ()
mainProgram = do input <- getUserInput
                 lift $ doLog input
                 liftIO $ putStrLn $ "Entry logged: " ++ input

However, this has the unfortunate effect that the function is no longer fully generic, because the order in which the two readers appear in the monad stack is locked. Maybe there is a cleaner way to achieve this, but I wasn't able to figure one out from the top of my head without sacrificing (even more) genericity.

Problem

I am trying to come up with a modular program design and I, once again, kindly request your help. As a follow-up to these following posts Monad Transformers vs passing Parameters and Large Scale Design in Haskell, I am trying to build two independent modules that use Monad Transformers but expose Monad-agnostic functions, then combine a Monad-agnostic function from each of these modules into a new Monad-agnostic function. I have been unable to run the combining function e.g. how do I call `mainProgram` using `runReaderT` in the example below ?. The subsidiary question is: is there a better way to achieve the same modular design goal ? The example has two mock modules (but compiles), one that performs logging and one that reads an user input and manipulates it. The combining function reads the user input, logs it and prints it. ``` {-# LANGUAGE FlexibleContexts #-} module Stackoverflow2 where import Control.Monad.Reader ---- ---- From Log Module - Writes the passed message in the log ---- data LogConfig = LC { logFile :: FilePath } doLog :: (MonadIO m, MonadReader LogConfig m) => String -> m () doLog _ = undefined ---- ---- From UserProcessing Module - Reads the user Input and changes it to the configured case ---- data MessageCase = LowerCase | UpperCase deriving (Show, Read) getUserInput :: (MonadReader MessageCase m, MonadIO m) => m String getUserInput = undefined ---- ---- Main program that combines the two ---- mainProgram :: (MonadReader MessageCase m, MonadReader LogConfig m, MonadIO m) => m () mainProgram = do input <- getUserInput doLog input liftIO $ putStrLn $ "Entry logged: " ++ input ```

Original source

Related problems