The ability to use functions 'catch', 'bracket', 'catchDyn', etc. in MonadIO other than IO itself has been a fairly frequently requested feature: http://www.haskell.org/pipermail/glasgow-haskell-users/2003-September/005660... http://haskell.org/pipermail/libraries/2003-February/000774.html The reason it is not implemented is because these functions cannot be defined for a general MonadIO. However, these functions can be easily defined for a large and interesting subset of MonadIO. The following code demonstrates that. It uses no extensions (other than those needed for the Monad Transformer Library itself), patches no compilers, and proposes no extensions. The generic catch has been useful in a database library (Takusen), where many operations work in a monad (ReaderT Session IO): IO with the environment containing the database session data. Many other foreign libraries have a pattern of passing around various handles, which are better hidden in a monad. Still, we should be able to handle IO errors and user exceptions that arise in these computations.
{-# OPTIONS -fglasgow-exts #-}
module CaughtMonadIO where
import Data.Typeable import Data.Dynamic import Control.Monad.Trans import Control.Exception hiding (catch, catchDyn) import qualified Control.Exception (catch) import Control.Monad.Reader import Control.Monad.Writer import Control.Monad.State import Control.Monad.RWS import Control.Monad.Error
--------------------- Tests
data MyException = MyException String deriving (Show, Typeable)
testfn True = throwDyn (MyException "thrown") testfn False = return True
testc m = catchDyn (m >>= return . show) (\ (MyException s) -> return s)
test1 = do tf True >>= print; tf False >>= print where tf x = runReaderT (runWriterT (testc (do tell "begin" r <- ask testfn r))) x
test2 = do tf True >>= print; tf False >>= print; where tf x = runReaderT (runErrorT (do r <- ask testfn r `catchDyn` (\ (MyException s) -> throwError s))) x
The implementation is quite trivial.
class MonadIO m => CaughtMonadIO m where gcatch :: m a -> (Exception -> m a) -> m a
instance CaughtMonadIO IO where gcatch = Control.Exception.catch
instance (CaughtMonadIO m, Error e) => CaughtMonadIO (ErrorT e m) where gcatch m f = mapErrorT (\m -> gcatch m (\e -> runErrorT $ f e)) m
The following is almost verbatim from `Control.Monad.Error' Section "MonadError instances for other monad transformers"
instance CaughtMonadIO m => CaughtMonadIO (ReaderT r m) where gcatch m f = ReaderT $ \r -> gcatch (runReaderT m r) (\e -> runReaderT (f e) r)
The following instances presume that an exception that occurs in 'm' discard the state accumulated since the beginning of 'm's execution. If that is not desired -- don't use StateT. Rather, allocate IORef and carry that _immutable_ value in a ReaderT. The accumulated state will thus persist. One can always use IORefs within any MonadIO.
instance (Monoid w, CaughtMonadIO m) => CaughtMonadIO (WriterT w m) where m `gcatch` h = WriterT $ runWriterT m `gcatch` \e -> runWriterT (h e)
instance CaughtMonadIO m => CaughtMonadIO (StateT s m) where m `gcatch` h = StateT $ \s -> runStateT m s `gcatch` \e -> runStateT (h e) s
instance (Monoid w, CaughtMonadIO m) => CaughtMonadIO (RWST r w s m) where m `gcatch` h = RWST $ \r s -> runRWST m r s `gcatch` \e -> runRWST (h e) r s
catchDyn :: (Typeable e, CaughtMonadIO m) => m a -> (e -> m a) -> m a catchDyn m f = gcatch m (\e -> maybe (throw e) f ((dynExceptions e) >>= fromDynamic))
oleg@pobox.com writes:
The implementation is quite trivial.
class MonadIO m => CaughtMonadIO m where gcatch :: m a -> (Exception -> m a) -> m a
instance CaughtMonadIO IO where gcatch = Control.Exception.catch
instance (CaughtMonadIO m, Error e) => CaughtMonadIO (ErrorT e m) where gcatch m f = mapErrorT (\m -> gcatch m (\e -> runErrorT $ f e)) m
Since the monad transformers in MTL all promote MonadError, you can also use throwError and catchError with instances of MonadIO. Currently, the error type associated with IO is IOError, not Exception, but it should be possible to work around that with a wrapper: newtype IO' a = IO' { unIO' :: IO a } deriving (Monad, Functor) instance MonadIO IO' where liftIO = IO' instance MonadError Exception IO' where throwError = IO' . throwIO m `catchError` h = IO' $ catch (unIO' m) (unIO' . h) -- David Menendez <zednenem@psualum.com> | "In this house, we obey the laws <http://www.eyrie.org/~zednenem> | of thermodynamics!"
On Wed, Feb 08, 2006 at 12:59:48AM -0500, David Menendez wrote:
Since the monad transformers in MTL all promote MonadError, you can also use throwError and catchError with instances of MonadIO. Currently, the error type associated with IO is IOError, not Exception, but it should be possible to work around that with a wrapper:
That's still not the same, because the point of Oleg's code seems to be to catch Exceptions within any CaughtMonadIO. With your approach, if there is any ErrorT transformer involved, catchError will catch the error from the outermost ErrorT, and general Exceptions will escape. See for example Oleg's test2. Andrew
oleg@pobox.com wrote:
The ability to use functions 'catch', 'bracket', 'catchDyn', etc. in MonadIO other than IO itself has been a fairly frequently requested feature: ... The reason it is not implemented is because these functions cannot be defined for a general MonadIO. However, these functions can be easily defined for a large and interesting subset of MonadIO.
IIRC, that subset is types that can implement this: class (MonadIO m) => StrictMonadIO m where getUnliftIO :: m (m a -> IO a)
On 2/8/06, Ashley Yakeley <ashley@semantic.org> wrote:
IIRC, that subset is types that can implement this:
class (MonadIO m) => StrictMonadIO m where getUnliftIO :: m (m a -> IO a)
You probably mean "m (forall a. m a -> IO a)" and that is not allowed. You have to CPS it: withUnliftIO :: ((forall a. m a -> IO a) -> m a) -> m a -- Taral <taralx@gmail.com> "Computer science is no more about computers than astronomy is about telescopes." -- Edsger Dijkstra
Taral wrote:
On 2/8/06, Ashley Yakeley <ashley@semantic.org> wrote:
IIRC, that subset is types that can implement this:
class (MonadIO m) => StrictMonadIO m where getUnliftIO :: m (m a -> IO a)
You probably mean "m (forall a. m a -> IO a)" and that is not allowed.
Actually, I think what I have is sufficient: -- liftIO :: (MonadIO m) => IO a -> m a -- catch :: IO a -> (Exception -> IO a) -> IO a gcatch :: (StrictMonadIO m) => m a -> (Exception -> m a) -> m a gcatch ma cc = do unliftIO <- getUnliftIO liftIO (catch (unliftIO ma) (\ex -> unliftIO (cc ex)))
participants (5)
-
Andrew Pimlott -
Ashley Yakeley -
David Menendez -
oleg@pobox.com -
Taral