Search code examples
haskellmonadsstate-monad

Best practice with Monad


I would like to know what can be considered as a best practice regarding the State monad. I'm also open to any other suggestion.

I have a binary file to parse. It contains different header that need to be parsed in order to be able to read the complete file.

So the headers can be parsed using only State from the parse.

data ParseState = ParseState {
   offset :: Int64
   buffer :: B.ByteString
   endianness :: Endianness
   pointerSize :: MachineWord
   positionStack :: [Int64]
}

This data is then used in a State monad

type Parser a = State ParseState a

This can perfectly suite the parsing of the header. But as soon as I want to parse the complete file I need information from the header to be able to correctly read the file.

data Header = Header {
    txtOffset :: Int64,
    stringOffset :: Int64
}

I need the header information to continue parsing the file.

My idea was to use a new state monad that sit on top of the previous one. So I have a new StateT monad:

type ParserFullState a = StateT Header (State ParserState) a

Thus I can continue and build a whole set of parser function using the new state transformer. I could also do it differently and add the header to the original ParseState data.

The pros I can see at adding the header back into the ParserState are the following:

  1. The return type of parser function is uniform
  2. No need to call lift to access the parser primitive.

The cons I can see are:

  1. There is no distinction between higher level parser and lower primitive.
  2. We can not tell clearly when the header is fully parse or when it is not. Thus making the parser modification more fragile.

What is your suggestion? Should I use the state transformer of should I add the header to the original state or anythings else?

Thanks.


Solution

  • Generally, I would advice against using multiple layers of State (or indeed any transformer). Transformers are great, but in thicker clusters they do get confusing, especially when the type system can't properly decide which MonadState to use anymore.

    Nevertheless, in you specific case another transformer is actually a good idea, but not a StateT: the header information shouldn't change during the further parsing of the file, so it should really just be a ReaderT, shouldn't it?

    type ParserFullState = ReaderT Header (State ParserState)
    

    or equivalently

    type ParserFullState = RSS Header () ParserState