hexpr-0.0.0.0: Control/Monad/Errors.hs
{-| 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