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.