{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UndecidableInstances #-} import Control.Monad.Error.Class import Control.Monad.IO.Class import Control.Monad.IO.Unlift import Control.Exception (Exception, catch, throwIO) import Control.Monad (liftM, ap) import Data.Typeable (Typeable) -- | A newtype wrapper that implements MonadError using IO exceptions newtype ErrorT e m a = ErrorT { runErrorT :: m a } deriving (Functor, Applicative, Monad, MonadIO, MonadUnliftIO) -- | Custom exception wrapper to hold our error type data ErrorException e = ErrorException e deriving (Show, Eq, Typeable) instance (Show e, Typeable e) => Exception (ErrorException e) -- | MonadError instance that uses IO exceptions under the hood instance (MonadUnliftIO m, Show e, Typeable e) => MonadError e (ErrorT e m) where throwError e = ErrorT $ liftIO $ throwIO (ErrorException e) catchError (ErrorT action) handler = ErrorT $ do withRunInIO $ \runInIO -> runInIO action `catch` (\(ErrorException e) -> runInIO $ runErrorT $ handler e) -- | Helper function to run an ErrorT computation runErrorTSafe :: (MonadUnliftIO m, Show e, Typeable e) => ErrorT e m a -> m (Either e a) runErrorTSafe (ErrorT action) = do withRunInIO $ \runInIO -> (Right <$> runInIO action) `catch` (\(ErrorException e) -> return $ Left e) -- | Example usage example :: ErrorT String IO Int example = do x <- return 10 if x > 5 then throwError "Number too large!" else return x exampleWithCatch :: ErrorT String IO Int exampleWithCatch = do result <- catchError example $ \err -> do liftIO $ putStrLn $ "Caught error: " ++ err return 0 return result -- | Test the implementation main :: IO () main = do putStrLn "Testing MonadError implementation:" -- Test throwing an error result1 <- runErrorTSafe example putStrLn $ "Example result: " ++ show result1 -- Test catching an error result2 <- runErrorTSafe exampleWithCatch putStrLn $ "Example with catch result: " ++ show result2