I have some experience with simple (single-focus) Haskell programs but now I am trying to write bigger applications in Haskell. I am having trouble structuring the application.
Say, we have an application with three modules (conceptually) - Main, Logic, and AppIO. Main starts the program and uses functions in Logic and AppIO to do the work. Logic abstracts the app logic and we need multiple implementations of it. Which Logic implementation is used by Main is decided by user configuration. AppIO is for functions that talk to the outside world.
My Main module has a newtype App = App { unapp :: ReaderT r (ExceptT e m) a } monad transformer. Similarly, Logic and AppIO have their own monad transformer stacks. I am thinking of creating an App monad transformer with Logic and AppIO transformers at the base.
At this point I have some questions.
- Is this a correct way to structure Haskell applications that have multiple monadic modules?
- Should
LogicandAppIOhave their own monad transformer stacks or should they haveAppin their type signatures? I don't likeAppto be in module types as I think modules should be agnostic to each other. How will I giveLogicits own state if I don't make it a transformer in itself? - If
LogicandAppIOhave their ownExceptTmonads in their transformer stacks for error handling with their own error types i.e.MonadError LogicErrorandMonadError IOError, how would they work withApplevelExceptT? Do I need to writeliftfunctions? If I writeliftfunctions then how do I have internal state (for exampleRandomGen) inLogicthat is updated on function calls? From what I understandliftwill run the monad and so its state is lost? - How do I write
Logicso that it has multiple implementations that may be swapped with each other without having to change anything else? Should I define aclass Logic? Or should it be adata Logicwith its interface functions as members?