Search code examples
haskellmonadsmonad-transformersstate-monad

How to kickstart monad transformer stack from main?


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?


Solution

  • 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).