
I'm working on a library which needs to operate on large data sets, so I'd like to use lazy values. The library consists of pure functions from Text to [Event] and back. Normally, I use Maybe or Either for error handling in pure code, but using these precludes lazy evaluation. Using exceptions requires any errors to be handled in IO, which is annoying. The "idealized" signatures of the functions are: ---------------------------------------- import qualified Data.Text.Lazy as TL data Event = EventA | EventB | EventC parse :: TL.Text -> Either ParseError [Event] serialize :: [Event] -> Either SerializeError TL.Text ---------------------------------------- I've considered two possible error handling modes, both adapted from procedural language style. The first is simply including errors in the event list. ---------------------------------------- import qualified Data.Text as T parse :: TL.Text -> [Either ParseError Event] serialize :: [Event] -> [Either SerializeError T.Text] -- use TL.fromChunks ---------------------------------------- The second uses monadic callbacks, based on side effects: ---------------------------------------- parse :: Monad m => (Event -> m ()) -> (ParseError -> m ()) -> TL.Text -> m () serialize :: Monad m => (T.Text -> m ()) -> (SerializeError -> m ()) -> [Event] -> m () ---------------------------------------- The main problem I see with these is that they don't indicate or enforce that an error terminates the event/text streams. The first allows multiple errors in a row, or events to follow an error. The second just "feels" ugly, because using it in pure code requires clients to build a Writer (possibly wrapped with ErrorT) and deal with the associated plumbing. Is there any sort of standard idiom for handling this problem? It seems that somebody must have run across it before, but the resources I can find on lazy error handling all assume the code is impure (returning IO, etc).