I build a project based on the ReaderT design pattern. Instead of using a typeclass approach for dependency injection, I choose to use simple injection of handlers as function arguments. This part works fine as one is able to construct a dependency tree statically and define an environment dynamically.
The environment may contain configuration as well as a logging effect :: String -> IO (), an effect of time :: IO UTCDate etc. Consider the following minified example
import Control.Monad.Reader (runReaderT, liftIO, reader, MonadReader, MonadIO)
data SomeEnv
= SomeEnv
{ a :: Int
, logger :: String -> IO ()
}
class HasLogger a where
getLogger :: a -> (String -> IO())
instance HasLogger SomeEnv where
getLogger = logger
myFun :: (MonadIO m, MonadReader e m, HasLogger e) => Int -> m Int
myFun x = do
logger <- reader getLogger
liftIO $ logger "I'm going to multiply a number by itself!"
return $ x * x
doIt :: IO Int
doIt = runReaderT (myFun 1337) (SomeEnv 13 putStrLn)
Is it possible to generalize over the effect of the logger?
logger :: String -> m ()
With the motivation to use a logger which fits into the monad stack
myFun x = do
logger <- reader getLogger
logger "I'm going to multiply a number by itself!"
return $ x * x