this sort of a follow-up question to my previous one: State and IO Monads
My goal is to create a simple text-editor for files. I already have an Editor
component that nicely encapsulates all editing actions on an underlying data structure.
Thanks to the answers to my previous question, I was able to refactor my program so that I now have a nice monad transformer stack:
type Session = StateT AppState (StateT Editor IO)
AppState
holds the global state of the application (currently open file, etc...), while Editor
represents the inner state of the editing-component of the app (where the caret is at, etc...). I have a function that is the main driver of the application:
eventLoop :: Session ()
So far so good, however now I don't know how I can actually kick-start my transformer stack from my main
function? Main has to return something in the IO
monad which is at the very base of my stack. My guess is that I have to initialize my AppState
and then do something like:
main = do
let initialAppState = ...
return $ runStateT eventLoop initialAppState
But where do I initialize my Editor
now?
The main thing confusing me is that before the refactoring, Editor
was simply a member of AppState
:
data AppState = { editor :: Editor , ... }
but now it has moved out of AppState
and become somewhat of a sibling on the transformer stack. Shouldn't Editor
still be part of AppState
because modifying it means modifying the overall state?
How do I properly initialize my Session
with both AppState
and Editor
and then run it from my main
?
how I can actually kick-start my transformer stack from my main function?
main =
flip evalStateT initialAppState $
flip evalStateT initialEditorState $
eventLoop
where
initialAppState =
error "Define me"
initialEditorState =
error "Define me"
Shouldn't Editor still be part of AppState because modifying it means modifying the overall state?
It depends.
Remember that the purpose of Monad Transformer is to extend the functionality in an ad-hoc way? By ad-hoc I mean, without rewriting the existing codebase, but by adding to it. So, if you already have isolated APIs of Editor and AppState, it's easier to just combine them in another "dome" module using the transformer stack.
OTOH, from the initial architecture point of view, it totally makes sense that AppState is a data-structure that encompasses Editor (I'd name it EditorState) among other things. In such a cas the API of AppState should encapsulate the API of Editor. The "lens" library will help you a lot to work with such composite data-structures (although I must mention that it has a steep learning curve).