    {-# OPTIONS -fallow-undecidable-instances #-}
    -- Needed for the same reasons as in Reader, State etc

    {- |
    Module      :  Control.Monad.Error
    Copyright   :  (c) Michael Weber <michael.weber@post.rwth-aachen.de> 2001,
                   (c) Jeff Newbern 2003-2006,
                   (c) Andriy Palamarchuk 2006
    License     :  BSD-style (see the file libraries/base/LICENSE)

    Maintainer  :  libraries@haskell.org
    Stability   :  experimental
    Portability :  non-portable (multi-parameter type classes)

    [Computation type:] Computations which may fail or throw exceptions.

    [Binding strategy:] Failure records information about the cause\/location
    of the failure. Failure values bypass the bound function,
    other values are used as inputs to the bound function.

    [Useful for:] Building computations from sequences of functions that may fail
    or using exception handling to structure error handling.

    [Zero and plus:] Zero is represented by an empty error and the plus operation
    executes its second argument if the first fails.

    [Example type:] @'Data.Either' String a@

    The Error monad (also called the Exception monad).
    -}

    {-
      Rendered by Michael Weber <mailto:michael.weber@post.rwth-aachen.de>,
      inspired by the Haskell Monad Template Library from
        Andy Gill (<http://www.cse.ogi.edu/~andy/>)
    -}
    module Control.Monad.Error (
        module Control.Monad.Error.Class,
        ErrorT(..),
        mapErrorT,
        module Control.Monad,
        module Control.Monad.Fix,
        module Control.Monad.Trans,
        -- * Example 1: Custom Error Data Type
        -- $customErrorExample

        -- * Example 2: Using ErrorT Monad Transformer
        -- $ErrorTExample
      ) where

    import Control.Monad
    import Control.Monad.Cont.Class
    import Control.Monad.Error.Class
    import Control.Monad.Fix
    import Control.Monad.RWS.Class
    import Control.Monad.Reader.Class
    import Control.Monad.State.Class
    import Control.Monad.Trans
    import Control.Monad.Writer.Class

    import Control.Monad.Instances ()
    import System.IO

    instance MonadPlus IO where
        mzero       = ioError (userError "mzero")
        m `mplus` n = m `catch` \_ -> n

    instance MonadError IOError IO where
        throwError = ioError
        catchError = catch

    -- ---------------------------------------------------------------------------
    -- Our parameterizable error monad

    instance (Error e) => Monad (Either e) where
        return        = Right
        Left  l >>= _ = Left l
        Right r >>= k = k r
        fail msg      = Left (strMsg msg)

    instance (Error e) => MonadPlus (Either e) where
        mzero            = Left noMsg
        Left _ `mplus` n = n
        m      `mplus` _ = m

    instance (Error e) => MonadFix (Either e) where
        mfix f = let
            a = f $ case a of
                Right r -> r
                _       -> error "empty mfix argument"
            in a

    instance (Error e) => MonadError e (Either e) where
        throwError             = Left
        Left  l `catchError` h = h l
        Right r `catchError` _ = Right r

    {- |
    The error monad transformer. It can be used to add error handling to other
    monads.

    The @ErrorT@ Monad structure is parameterized over two things:

     * e - The error type.

     * m - The inner monad.

    Here are some examples of use:

    > -- wraps IO action that can throw an error e
    > type ErrorWithIO e a = ErrorT e IO a
    > ==> ErrorT (IO (Either e a))
    >
    > -- IO monad wrapped in StateT inside of ErrorT
    > type ErrorAndStateWithIO e s a = ErrorT e (StateT s IO) a
    > ==> ErrorT (StateT s IO (Either e a))
    > ==> ErrorT (StateT (s -> IO (Either e a,s)))
    -}

    newtype ErrorT e m a = ErrorT { runErrorT :: m (Either e a) }

    mapErrorT :: (m (Either e a) -> n (Either e' b))
              -> ErrorT e m a
              -> ErrorT e' n b
    mapErrorT f m = ErrorT $ f (runErrorT m)

    instance (Monad m) => Functor (ErrorT e m) where
        fmap f m = ErrorT $ do
            a <- runErrorT m
            case a of
                Left  l -> return (Left  l)
                Right r -> return (Right (f r))

    instance (Monad m, Error e) => Monad (ErrorT e m) where
        return a = ErrorT $ return (Right a)
        m >>= k  = ErrorT $ do
            a <- runErrorT m
            case a of
                Left  l -> return (Left l)
                Right r -> runErrorT (k r)
        fail msg = ErrorT $ return (Left (strMsg msg))

    instance (Monad m, Error e) => MonadPlus (ErrorT e m) where
        mzero       = ErrorT $ return (Left noMsg)
        m `mplus` n = ErrorT $ do
            a <- runErrorT m
            case a of
                Left  _ -> runErrorT n
                Right r -> return (Right r)

    instance (MonadFix m, Error e) => MonadFix (ErrorT e m) where
        mfix f = ErrorT $ mfix $ \a -> runErrorT $ f $ case a of
            Right r -> r
            _       -> error "empty mfix argument"

    instance (Monad m, Error e) => MonadError e (ErrorT e m) where
        throwError l     = ErrorT $ return (Left l)
        m `catchError` h = ErrorT $ do
            a <- runErrorT m
            case a of
                Left  l -> runErrorT (h l)
                Right r -> return (Right r)

    -- ---------------------------------------------------------------------------
    -- Instances for other mtl transformers

    instance (Error e) => MonadTrans (ErrorT e) where
        lift m = ErrorT $ do
            a <- m
            return (Right a)

    instance (Error e, MonadIO m) => MonadIO (ErrorT e m) where
        liftIO = lift . liftIO

    instance (Error e, MonadCont m) => MonadCont (ErrorT e m) where
        callCC f = ErrorT $
            callCC $ \c ->
            runErrorT (f (\a -> ErrorT $ c (Right a)))

    instance (Error e, MonadRWS r w s m) => MonadRWS r w s (ErrorT e m)

    instance (Error e, MonadReader r m) => MonadReader r (ErrorT e m) where
        ask       = lift ask
        local f m = ErrorT $ local f (runErrorT m)

    instance (Error e, MonadState s m) => MonadState s (ErrorT e m) where
        get = lift get
        put = lift . put

    instance (Error e, MonadWriter w m) => MonadWriter w (ErrorT e m) where
        tell     = lift . tell
        listen m = ErrorT $ do
            (a, w) <- listen (runErrorT m)
            case a of
                Left  l -> return $ Left  l
                Right r -> return $ Right (r, w)
        pass   m = ErrorT $ pass $ do
            a <- runErrorT m
            case a of
                Left  l      -> return (Left  l, id)
                Right (r, f) -> return (Right r, f)

    {- $customErrorExample
    Here is an example that demonstrates the use of a custom 'Error' data type with
    the 'throwError' and 'catchError' exception mechanism from 'MonadError'.
    The example throws an exception if the user enters an empty string
    or a string longer than 5 characters. Otherwise it prints length of the string.

    >-- This is the type to represent length calculation error.
    >data LengthError = EmptyString  -- Entered string was empty.
    >          | StringTooLong Int   -- A string is longer than 5 characters.
    >                                -- Records a length of the string.
    >          | OtherError String   -- Other error, stores the problem description.
    >
    >-- We make LengthError an instance of the Error class
    >-- to be able to throw it as an exception.
    >instance Error LengthError where
    >  noMsg    = OtherError "A String Error!"
    >  strMsg s = OtherError s
    >
    >-- Converts LengthError to a readable message.
    >instance Show LengthError where
    >  show EmptyString = "The string was empty!"
    >  show (StringTooLong len) =
    >      "The length of the string (" ++ (show len) ++ ") is bigger than 5!"
    >  show (OtherError msg) = msg
    >
    >-- For our monad type constructor, we use Either LengthError
    >-- which represents failure using Left LengthError
    >-- or a successful result of type a using Right a.
    >type LengthMonad = Either LengthError
    >
    >main = do
    >  putStrLn "Please enter a string:"
    >  s <- getLine
    >  reportResult (calculateLength s)
    >
    >-- Wraps length calculation to catch the errors.
    >-- Returns either length of the string or an error.
    >calculateLength :: String -> LengthMonad Int
    >calculateLength s = (calculateLengthOrFail s) `catchError` Left
    >
    >-- Attempts to calculate length and throws an error if the provided string is
    >-- empty or longer than 5 characters.
    >-- The processing is done in Either monad.
    >calculateLengthOrFail :: String -> LengthMonad Int
    >calculateLengthOrFail [] = throwError EmptyString
    >calculateLengthOrFail s | len > 5 = throwError (StringTooLong len)
    >                        | otherwise = return len
    >  where len = length s
    >
    >-- Prints result of the string length calculation.
    >reportResult :: LengthMonad Int -> IO ()
    >reportResult (Right len) = putStrLn ("The length of the string is " ++ (show len))
    >reportResult (Left e) = putStrLn ("Length calculation failed with error: " ++ (show e))
    -}

    {- $ErrorTExample
    @'ErrorT'@ monad transformer can be used to add error handling to another monad.
    Here is an example how to combine it with an @IO@ monad:

    >import Control.Monad.Error
    >
    >-- An IO monad which can return String failure.
    >-- It is convenient to define the monad type of the combined monad,
    >-- especially if we combine more monad transformers.
    >type LengthMonad = ErrorT String IO
    >
    >main = do
    >  -- runErrorT removes the ErrorT wrapper
    >  r <- runErrorT calculateLength
    >  reportResult r
    >
    >-- Asks user for a non-empty string and returns its length.
    >-- Throws an error if user enters an empty string.
    >calculateLength :: LengthMonad Int
    >calculateLength = do
    >  -- all the IO operations have to be lifted to the IO monad in the monad stack
    >  liftIO $ putStrLn "Please enter a non-empty string: "
    >  s <- liftIO getLine
    >  if null s
    >    then throwError "The string was empty!"
    >    else return $ length s
    >
    >-- Prints result of the string length calculation.
    >reportResult :: Either String Int -> IO ()
    >reportResult (Right len) = putStrLn ("The length of the string is " ++ (show len))
    >reportResult (Left e) = putStrLn ("Length calculation failed with error: " ++ (show e))
    -}