{-| In many error-checking algorithms, it is desireable to report several errors rather than simply terminate on detecting the first error. Where 'Either' and 'Error' terminates on the first error, 'Errors' can recover at specified points and continue error-checking. Even after a recovery, the prior errors are logged. If any errors occured during error-checking, this si an error in the whole computation. -} module Control.Monad.Errors ( -- * Errors Monad Errors , runErrors -- * Error Reporting Functions , err , err1 , choice , recover , recover_ , mapRecover , unrecover -- ** Hoisting Functions , hoistMaybe , hoistEither , hoistEither1 -- * Errors Transformer , ErrorsT , runErrorsT ) where import Data.Monoid import Data.Either import Control.Applicative import Control.Monad import Control.Monad.Identity import Control.Monad.Writer import Control.Monad.Trans import Control.Monad.Trans.Either hiding (hoistEither) {-| Shortcut for 'ErrorsT' over the 'Identity' monad. -} type Errors e = ErrorsT e Identity {-| Computations that can collect multiple errors. -} newtype ErrorsT e m a = ErrorsT { unErrors :: m (Maybe e -> (Maybe a, Maybe e)) } {-| Perform an error-reporting computation. -} runErrors :: (Monoid e) => Errors e a -> Either e a runErrors = runIdentity . runErrorsT {-| Perform the error reporting part of a computation. -} runErrorsT :: (Monad m, Monoid e) => ErrorsT e m a -> m (Either e a) runErrorsT action = do innerAction <- unErrors action let res = innerAction Nothing return $ case res of (Just val, Nothing) -> Right val (_, Just errs) -> Left errs (Nothing, Nothing) -> error "Control.Monad.Errors: internal error" {-| Report an error. -} err :: (Monad m, Monoid e) => e -> ErrorsT e m a err msg = ErrorsT . return $ \e -> (Nothing, e <> Just msg) {-| Report one error accumulating in a list. -} err1 :: (Monad m) => e -> ErrorsT [e] m a err1 = err . (:[]) {-| Try several alternatives (in order), but if none succeed, raise the passed error. -} choice :: (Monad m, Monoid e) => e -> [ErrorsT e m a] -> ErrorsT e m a choice e0 [] = err e0 choice e0 (a:as) = do res <- lift $ runErrorsT a case res of Left e0 -> choice e0 as Right val -> return val {-| If the action returns an error, relpace the result with a default. The error is still logged and reported at the end of the computation. -} recover :: (Monad m, Monoid e) => a -> ErrorsT e m a -> ErrorsT e m a recover replacement action = ErrorsT $ do res <- runErrorsT action return $ case res of Left err -> \e -> (Just replacement, e <> Just err) Right val -> \e -> (Just val, e) {-| As 'recover', but any successful result value does not matter. -} recover_ :: (Monad m, Monoid e) => ErrorsT e m a -> ErrorsT e m () recover_ action = recover () (const () <$> action) {-| Perform many error checks, recovering between each. The value at each index of the output list corresponds to the index of the input computation list. Error values are 'Nothing' in the output, successful values are wrapped in 'Just'. -} mapRecover :: (Monad m, Monoid e) => [ErrorsT e m a] -> ErrorsT e m [Maybe a] mapRecover actions = mapM (recover Nothing . (Just <$>)) actions {-| If any errors have been detected, cuase them to be loud again. -} unrecover :: (Monad m, Monoid e) => ErrorsT e m () unrecover = ErrorsT . return $ \e -> case e of Nothing -> (Just (), e) Just _ -> (Nothing, e) {-| Turn a 'Maybe' computation into an 'ErrorsT' computation. -} hoistMaybe :: (Monad m, Monoid e) => e -> Maybe a -> ErrorsT e m a hoistMaybe e = maybe (err e) return {-| Turn an 'Either' computation into an 'ErrorsT' computation. -} hoistEither :: (Monad m, Monoid e) => Either e a -> ErrorsT e m a hoistEither = either err return {-| Turn an 'Either' computation into an 'ErrorsT' computation when accumulating a list. -} hoistEither1 :: (Monad m) => Either e a -> ErrorsT [e] m a hoistEither1 = either err1 return instance (Monad m, Monoid e) => Functor (ErrorsT e m) where fmap = liftM instance (Monad m, Monoid e) => Applicative (ErrorsT e m) where pure = return (<*>) = ap instance (Monad m, Monoid e) => Monad (ErrorsT e m) where return x = ErrorsT . return $ \e -> (Just x, e) x >>= k = ErrorsT $ do eRes <- runErrorsT x case eRes of Left err -> return $ \e -> (Nothing, e <> Just err) Right val -> unErrors $ k val instance (Monoid e) => MonadTrans (ErrorsT e) where lift x = ErrorsT $ do x' <- x return $ \e -> (Just x', e) instance (MonadIO m, Monoid e) => MonadIO (ErrorsT e m) where liftIO = lift . liftIO