I am following the advise presented on this video by Ben Kolera for structuring modular Haskell applications.
It is suggested to have multiple monad transformers to make the application modular and organized. Use of custom liftModule functions is suggested to compose these monads together.
For example, we have a main App module and a Logic module.
newtype App a = App
{ unApp :: ExceptT AppError (ReaderT AppEnv IO) a }
newtype Logic a = Logic
{ unLogic :: ExceptT LogicError (Reader LogicEnv) a }
liftLogic is defined as follows.
runLogic :: LogicEnv -> Logic a -> Either LogicError a
liftLogic :: Logic a -> App a
liftLogic l = do
c <- asks appEnvLogic
either (throwError . AppLogicError) pure $ runLogic c l
With this approach, how do I give a module an internal state? If I put a StateT LogicState in Logic transformer then won't liftMonad run monad completely thus unwrapping it and destroying its internal state?
The only way I see is to leak the internal state of Logic to App which I think is anti-modularity as it forces App to take care of Logic's state.