I'm currently writing some pipes-core/attoparsec plumbing for a small project of mine. I want each parser to give a pipe that awaits ByteString
input to the parser and yields any parsed values (restarting the parser). Without error handling it would thus have a type like
parserP :: Monad m => Parser a -> Pipe ByteString a m r
Now, I'm unsure about what to do with parse errors. My current ideas are to:
- to add the errors to the return type (i.e. return a value in
Either ParseError r
rather than justr
) - require the monad to provide the error handling mechanism (i.e. require the monad the pipe is taken over to implement
MonadError
) - force the monad to supply an error mechanism by taking the Pipe over
ErrorT e m a
for any monadm
- add parameters, letting the user specify the behaviour (something like
(ParseError -> P.Pipe ByteString a m r)
, and simply bind to the thus provided pipe in case of a parse error)
The first solution seems wrong, as using the return type of a pipe for error handling seems like rather a hack. It, for one thing, makes composition with the pipe uglier and seem to be more or less subsumed by the final solution (apart from possibly losing the ability of letting a downstream pipe being able to recover from an error by using tryAwait and stop awaiting values?).
The second solution seems wrong, though I can't quite put my finger on why. Possibly since it would(could?) also require taking a parameter translating the ParseError into whatever error type the monad has (unless we wish to require the monad to implement MonadError ParseError
, which seems like it would result in a lot of book keeping). Finally, I can't seem to remember seeing MonadError
around all that much, which would suggest that there is some issue with using it.
The third solution would work in my case, as the pipe will be part of a pipeline with a user specified monad(IO) that is not supposed to care about the parsing errors (it'll parse network data into a format yielded to a user specified type). But it doesn't seem all that elegant, and would, again, (possibly?) result in a lot of book keeping once used in any other context.
I haven't really thought through the final solution, but it seems somewhat convoluted.
I would be grateful for any thoughts on this particular case (I wouldn't be at all surprised if I'm way off and missing something obvious), and for any (more or less relevant) references to discussions on error handling in pipes(-core)/conduits/interatee e.t.c.
EDIT: Another possibility might be to take just a monadic action (rather than a full blown pipe), though I'm not quite sure whether it might just generalise, specialise or even be equivalent to the fourth one.