
contrary to monadic parsers, those continuation-based parsers had *two* continuations, one for success, one for failure. and that seemed to be a very natural match for the problem.
Two-continuations is a monad too, right?
yes, but my problem is not about giving them a monadic interface, but about getting more advantages than disadvantages out of that. the initial definitions are more complicated than using continuations directly, and usually, the monadic interface forces me to interleave handling of the two paths into a single thread of specifications. and while instances of the monadic combinators can be defined to thread information for both continuations, i still have to inject that information outside the monadic interface, and make sure that it doesn't get reset to nothing just because someone calls Monad fail (via a library function or pattern-match failure), or MonadPlus mzero (via things like guard). a second problem is that some of the advantages of monadic interfaces that i tend to rely on heavily, such as pattern-match failure handling in do-notation, are hardwired to the wrong part of the monadic interface (Monad fail). this is not something a non-monadic approach would offer any help with, though..
instance Monad m => Monad (ContErrorT m) where .. instance Monad m => MonadError Exception (ContErrorT m) where ..
Am I missing something really obvious here?
interesting. i tend to use Monad/MonadPlus for sequence/ alternative, rather than Monad/MonadError, but you're right: MonadError's method types are a more natural dual to Monad's. they don't have to be limited to Exception, either. it seems i should be using that class more. actually, i am using that class, via the ErrorT transformer, but i guess i thought of MonadError only as error handling in sequences (which is the way it is usually presented) rather than as fallthrough in alternatives. it would force me to decide in advance whether i want fallthrough branches (MonadError) or collections of alternatives (MonadPlus), whereas MonadPlus allowed me to delay that decision (using MonadPlus Maybe for fallthrough or MonadPlus [] for collections, for instance). hmm, perhaps one can abstract over that decision. now, if we could replace calls to Monad fail (especially, pattern match failure in do-notation) with calls to MonadError throwError, we might actually be getting somewhere (similarly, we'd need to replace MonadPlus-based guard with a MonadError-based variant). as a start, fail msg = throwError (PatternMatchError msg) isn't too far off, but it would be lying for fail called from library functions, not to mention that it would still be limited to Strings. but i've already used my own variant of catchError to limit information loss and preserve the most specific error message, so once again, MonadError seems a natural fit. all in all, your answer only strengthens my view that monadic programming would be more symmetric if we always had two continuations, one for failure, one for success. then do-notation translation could rely on both Monad and MonadError, using throwError instead of fail (it has been suggested to use mzero, with a separate MonadZero class, but MonadError may well be the better match). perhaps there'll come a time when the monadic aspects of haskell will need redesign, similar to the numeric aspects. thanks! this emphasized view of MonadError might help. claus