Skip to content

Instantly share code, notes, and snippets.

@edsko
Created January 22, 2019 15:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edsko/425d9f3d9b4bfc1204b5c67170b27166 to your computer and use it in GitHub Desktop.
Save edsko/425d9f3d9b4bfc1204b5c67170b27166 to your computer and use it in GitHub Desktop.
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Error handling
--
-- Intended for qualified import
--
-- > import Ouroboros.Storage.Util.ErrorHandling (ErrorHandling(..))
-- > import qualified Ouroboros.Storage.Util.ErrorHandling as EH
module Ouroboros.Storage.Util.ErrorHandling (
ErrorHandling(..)
, try
, monadError
, exceptions
, embed
, liftNewtype
, liftReader
, liftState
) where
import Control.Exception (Exception)
import qualified Control.Exception as E
import Control.Monad.Except (MonadError)
import qualified Control.Monad.Except as M
import Control.Monad.Reader (ReaderT (..), runReaderT)
import Control.Monad.State (StateT (..), runStateT)
import Data.Type.Coercion
-- | Reification of the 'MonadError' class
--
-- Unlike 'MonadError', it is perfectly fine to have multiple 'ErrorHandling'
-- objects available in a single context, as the caller can decide explicitly
-- which to call. Moreover, being regular records, they can have additional
-- information in the closure, if necessary (logging handles, for instance).
data ErrorHandling e m = ErrorHandling {
throwError :: forall a. e -> m a
, catchError :: forall a. m a -> (e -> m a) -> m a
}
try :: Monad m => ErrorHandling e m -> m a -> m (Either e a)
try ErrorHandling{..} act = (Right <$> act) `catchError` (return . Left)
monadError :: MonadError e m => ErrorHandling e m
monadError = ErrorHandling {
throwError = M.throwError
, catchError = M.catchError
}
exceptions :: Exception e => ErrorHandling e IO
exceptions = ErrorHandling {
throwError = E.throwIO
, catchError = E.catch
}
-- | Embed one kind of error in another
embed :: (e' -> e)
-> (e -> Maybe e')
-> ErrorHandling e m -> ErrorHandling e' m
embed intro elim ErrorHandling{..} = ErrorHandling{
throwError = \e -> throwError (intro e)
, catchError = \act handler -> catchError act $ \e ->
case elim e of
Nothing -> throwError e
Just e' -> handler e'
}
-- | Lift for a newtype
--
-- TODO: This would be much nicer with QuantifiedConstraints.
liftNewtype :: forall e m m'.
(forall a. Coercion (m a) (m' a))
-> ErrorHandling e m -> ErrorHandling e m'
liftNewtype c ErrorHandling{..} = ErrorHandling {
throwError = \err -> to $ throwError err
, catchError = \act handler -> to $ catchError (from act) (\e -> from $ handler e)
}
where
to :: forall a. m a -> m' a
to = coerceWith c
from :: forall a. m' a -> m a
from = coerceWith (sym c)
-- | Lift for a reader monad
liftReader :: proxy env -> ErrorHandling e m -> ErrorHandling e (ReaderT env m)
liftReader _ ErrorHandling{..} = ErrorHandling{
throwError = \err -> ReaderT $ \_env ->
throwError err
, catchError = \act handler -> ReaderT $ \env ->
catchError (runReaderT act env) $ \e ->
runReaderT (handler e) env
}
liftState :: proxy st -> ErrorHandling e m -> ErrorHandling e (StateT st m)
liftState _ ErrorHandling{..} = ErrorHandling{
throwError = \err -> StateT $ \_st ->
throwError err
, catchError = \act handler -> StateT $ \st ->
catchError (runStateT act st) $ \e ->
runStateT (handler e) st
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment