[Haskell] New monadic implementation of Control.Exception

Brian Hulley brianh at metamilk.com
Tue Apr 25 14:03:40 EDT 2006


The pipermail archives deleted my previous attachment because it had a .hs 
extension (!!! :-) ), so I've renamed it to .txt so that it will persist for 
the future of the earth evolution (who needs an expensive burial plot when 
you can just write a Haskell module instead).

It is also now attached to the trac ticket 
http://hackage.haskell.org/trac/haskell-prime/ticket/110 so if you find any 
bugs in the module, you could update the version attached there (or attach a 
new bug-fixed version) - not that there will be any bugs of course :-)

Best regards, Brian.
-------------- next part --------------
-----------------------------------------------------------------------------
-- |
-- Module      :  Prime.Exception
-- Copyright   :  (c) The University of Glasgow 2001
--                (c) Brian Hulley 2006
-- License     :  BSD-style (see the file libraries/base/LICENSE)
-- 
-- Maintainer  :  -- not in the library at the moment!
-- Stability   :  experimental
-- Portability :  non-portable
--
-- This module provides support for raising and catching both built-in
-- and user-defined exceptions in user defined monads. It is adapted from
-- Control.Exception and extends the code supplied by oleg at pobox.com on the
-- Haskell mailing list to lift all functions in Control.Exception to all
-- monads formed by applying common monad transformers to monads based on IO.
--
-- Refs: <http://www.haskell.org/pipermail/haskell/2006-February/017547.html>
--
-- To use this you should hide all the conflicting Prelude functions or
-- disable implicit Prelude and use a minimal Prelude as suggested in
-- <http://hackage.haskell.org/trac/haskell-prime/wiki/Prelude>
--
-- THIS MODULE HAS NOT BEEN FULLY TESTED AND IS JUST PROVIDED AS AN
-- EXAMPLE FOR DISCUSSION. In particular, you should read Oleg's comments above
-- regarding semantics of the different monads under exceptions, and if in
-- doubt, consider ReaderT instead of StateT.
--
-- If you find any bugs in this module, please add a note to the Haskell' ticket
-- or send an email to brianh at metamilk.com
-----------------------------------------------------------------------------

module Prime.Exception
    ( MonadException(..)
    
    , C.Exception(..)
    , C.IOException
    , C.ArithException(..)
    , C.ArrayException(..)
    , C.AsyncException(..)

    , C.mapException
    , C.ioErrors
    , C.arithExceptions
    , C.errorCalls
    , C.dynExceptions
    , C.assertions
    , C.asyncExceptions
    , C.userErrors

    , C.assert

    , ioError
    , evaluate
    , getUncaughtExceptionHandler

    , C.throw
    , C.throwDyn

    , throwM
    , throwTo
    , throwDynTo

    , handle
    , handleDyn
    , handleJust
    , catchDyn
    , catchJust
    , try
    , tryJust
    , bracket
    , bracket_
    , finally

    ) where

import Prelude hiding (ioError, catch)
import qualified Control.Exception as C
import Control.Monad.Trans
import Control.Monad.State
import Control.Monad.Reader
import Control.Monad.Writer
import Control.Monad.RWS
import Control.Monad.Error
import Control.Monad.List
import Control.Concurrent (ThreadId)
import Data.Typeable
import Data.Monoid
import Data.Dynamic

type Exception = C.Exception

ioError :: MonadIO m => IOError -> m a
ioError i = liftIO $ C.ioError i

evaluate :: MonadIO m => a -> m a
evaluate a = liftIO $ C.evaluate a

getUncaughtExceptionHandler :: MonadIO m => m (Exception -> m ())
getUncaughtExceptionHandler = do
            e_io <- liftIO $ C.getUncaughtExceptionHandler
            return $ \e -> liftIO (e_io e)

throwM :: MonadIO m => Exception -> m a
throwM e = liftIO $ C.throwIO e

throwTo :: MonadIO m => ThreadId -> Exception -> m ()
throwTo t e = liftIO $ C.throwTo t e

throwDynTo :: (MonadIO m, Typeable exception) => ThreadId -> exception -> m ()
throwDynTo t e = liftIO $ C.throwDynTo t e

-- | It is debatable whether or not @MonadException@ should derive from @MonadIO@ since
-- none of the functions require @MonadIO at . However a @MonadException@ context will
-- usually also involve use of @liftIO@ or @throwM@ etc which needs a @MonadIO@

class MonadIO m => MonadException m where
    catch :: m a -> (Exception -> m a) -> m a

    block, unblock :: m a -> m a

    -- It is debatable whether or not this very low level function should be
    -- part of the class at all or should just remain tied to the IO monad
    setUncaughtExceptionHandler :: (Exception -> m ()) -> m ()


instance MonadException IO where
    catch = C.catch
    block = C.block
    unblock = C.unblock
    setUncaughtExceptionHandler = C.setUncaughtExceptionHandler

-- In the code that follows, om is used to mean "outer monad" so that it is
-- clearly distinguished from m which is the type of the inner monad
-- Also, "e_om" represents the handler so that it's easy to see that "e_om e" = an outer monad

instance MonadException m => MonadException (StateT s m) where
    catch om e_om = StateT $ \s -> catch (runStateT om s) (\e -> runStateT (e_om e) s)

    block om = StateT $ \s -> block (runStateT om s)
    unblock om = StateT $ \s -> unblock (runStateT om s)    

    setUncaughtExceptionHandler e_om = StateT $ \s -> do
                                                        setUncaughtExceptionHandler $ \e -> do
                                                                runStateT (e_om e) s
                                                                return ()
                                                        return ((), s)


instance MonadException m => MonadException (ReaderT r m) where
    catch om e_om = ReaderT $ \r -> catch (runReaderT om r) (\e -> runReaderT (e_om e) r)

    block om = ReaderT $ \r -> block (runReaderT om r)
    unblock om = ReaderT $ \r -> unblock (runReaderT om r)

    setUncaughtExceptionHandler e_om = ReaderT $ \r ->
                                            setUncaughtExceptionHandler (\e -> runReaderT (e_om e) r)


instance (MonadException m, Monoid w) => MonadException (WriterT w m) where
    catch om e_om = WriterT $ catch (runWriterT om) (\e -> runWriterT (e_om e))

    block om = WriterT $ block (runWriterT om)
    unblock om = WriterT $ unblock (runWriterT om)

    setUncaughtExceptionHandler e_om = do
                                (_,w) <- listen (return ())
                                WriterT $ do
                                            setUncaughtExceptionHandler (\e -> do
                                                runWriterT (e_om e)
                                                return ())
                                            return ((), w)

                                
instance (MonadException m, Monoid w) => MonadException (RWST r w s m) where
    catch om e_om = RWST $ \r s -> catch (runRWST om r s) (\e -> runRWST (e_om e) r s)

    block om = RWST $ \r s -> block (runRWST om r s)
    unblock om = RWST $ \r s -> unblock (runRWST om r s)

    setUncaughtExceptionHandler e_om = do
                                (_,w) <- listen (return ())
                                RWST $ \r s -> do
                                            setUncaughtExceptionHandler (\e -> do
                                                runRWST (e_om e) r s
                                                return ())
                                            return ((),s,w)


instance (MonadException m, Error e) => MonadException (ErrorT e m) where
    catch om e_om = ErrorT $ catch (runErrorT om) (\e -> runErrorT (e_om e))
    
    block om = ErrorT $ block (runErrorT om)
    unblock om = ErrorT $ unblock (runErrorT om)

    setUncaughtExceptionHandler e_om = ErrorT $ do
                                                    setUncaughtExceptionHandler (\e -> do
                                                        runErrorT (e_om e)
                                                        return ())
                                                    return (Right ())


instance MonadException m => MonadException (ListT m) where
    catch om e_om = ListT $ catch (runListT om) (\e -> runListT (e_om e))

    block om = ListT $ block (runListT om)
    unblock om = ListT $ unblock (runListT om)

    setUncaughtExceptionHandler e_om = ListT $ do
                                                    setUncaughtExceptionHandler (\e -> do
                                                        runListT (e_om e)
                                                        return ())
                                                    return [()]
                                    

-- The following are pasted from Control.Exception with IO replaced by an instance of MonadException

catchDyn :: (Typeable exception, MonadException m) => m a -> (exception -> m a) -> m a
catchDyn m k = catch m handle
    where handle ex = case ex of
                        (C.DynException dyn) ->
                            case fromDynamic dyn of
                                Just exception  -> k exception
                                Nothing -> C.throw ex
                        _ -> C.throw ex


handle :: MonadException m => (Exception -> m a) -> m a -> m a
handle x y = catch y x

handleDyn :: (Typeable exception, MonadException m) => (exception -> m a) -> m a -> m a
handleDyn x y = catchDyn y x
    
handleJust :: MonadException m => (Exception -> Maybe b) -> (b -> m a) -> m a -> m a
handleJust x y z = catchJust x z y

bracket :: MonadException m => m a -> (a -> m b) -> (a -> m c) -> m c
bracket before after thing =
  block $ do
    a <- before 
    r <- catch 
              (unblock (thing a))
              (\e -> do { after a; C.throw e })
    after a
    return r

bracket_ :: MonadException m => m a -> m b -> m c -> m c
bracket_ before after thing = bracket before (const after) (const thing)

finally :: MonadException m => m a -> m b -> m a
finally a sequel =
    block $ do
        r <- catch 
                 (unblock a)
                 (\e -> do { sequel; C.throw e })
        sequel
        return r

catchJust :: MonadException m => (Exception -> Maybe b) -> m a -> (b -> m a) -> m a
catchJust p a handler = catch a handler'
  where handler' e = case p e of 
                        Nothing -> C.throw e
                        Just b  -> handler b

try :: MonadException m => m a -> m (Either Exception a)
try a = catch (a >>= \ v -> return (Right v)) (\e -> return (Left e))

tryJust :: MonadException m => (Exception -> Maybe b) -> m a -> m (Either b a)
tryJust p a = do
  r <- try a
  case r of
    Right v -> return (Right v)
    Left  e -> case p e of
                    Nothing -> C.throw e
                    Just b  -> return (Left b)


More information about the Haskell mailing list