-
-
Save vagarenko/0a8725dfa2ebdd31457f4f7fd7b6408b to your computer and use it in GitHub Desktop.
Lightweight checked exceptions in Haskell without `unsafeCoerce`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# OPTIONS_GHC -Wall -fno-warn-unused-binds #-} | |
{-# LANGUAGE CPP #-} | |
{-# LANGUAGE FlexibleContexts #-} | |
{-# LANGUAGE IncoherentInstances #-} | |
{-# LANGUAGE RankNTypes #-} | |
{-# LANGUAGE ScopedTypeVariables #-} | |
#if __GLASGOW_HASKELL__ >= 708 | |
{-# LANGUAGE RoleAnnotations #-} | |
#endif | |
module Checked ( | |
Throws -- opaque | |
-- ** base | |
, throwChecked | |
, catchChecked | |
-- ** exceptions | |
, throwCheckedM | |
, catchCheckedM | |
-- ** lifted-base | |
, throwCheckedL | |
, catchCheckedL | |
) where | |
import Control.Exception (Exception) | |
import Control.Monad.Base (MonadBase) | |
import Control.Monad.Trans.Control (MonadBaseControl) | |
import Control.Monad.Catch (MonadThrow, MonadCatch) | |
import qualified Control.Exception as Base | |
import qualified Control.Exception.Lifted as Lifted | |
import qualified Control.Monad.Catch as Exceptions | |
#if __GLASGOW_HASKELL__ >= 708 | |
import GHC.Prim (coerce) | |
#else | |
import Unsafe.Coerce (unsafeCoerce) | |
#endif | |
{------------------------------------------------------------------------------- | |
Basic infrastructure | |
-------------------------------------------------------------------------------} | |
class Throws e where | |
#if __GLASGOW_HASKELL__ >= 708 | |
type role Throws representational | |
#endif | |
unthrow :: proxy e -> (Throws e => a) -> a | |
unthrow _ = unWrap . coerceWrap . Wrap | |
{------------------------------------------------------------------------------- | |
Base exceptions | |
-------------------------------------------------------------------------------} | |
throwChecked :: (Exception e, Throws e) => e -> IO a | |
throwChecked = Base.throwIO | |
catchChecked :: forall a e. Exception e | |
=> (Throws e => IO a) -> (e -> IO a) -> IO a | |
catchChecked act = Base.catch (unthrow (Proxy :: Proxy e) act) | |
{------------------------------------------------------------------------------- | |
Using the 'exceptions' library | |
This should be an independent library so that we don't pull in an | |
unnecessary dependency | |
-------------------------------------------------------------------------------} | |
throwCheckedM :: (Exception e, Throws e, MonadThrow m) => e -> m a | |
throwCheckedM = Exceptions.throwM | |
catchCheckedM :: forall a e m. (Exception e, MonadCatch m) | |
=> (Throws e => m a) -> (e -> m a) -> m a | |
catchCheckedM act = Exceptions.catch (unthrow (Proxy :: Proxy e) act) | |
{------------------------------------------------------------------------------- | |
Using the 'lifted-base' library | |
As above, should be an independent library | |
-------------------------------------------------------------------------------} | |
throwCheckedL :: (Exception e, Throws e, MonadBase IO m) => e -> m a | |
throwCheckedL = Lifted.throw | |
catchCheckedL :: forall a e m. (Exception e, MonadBaseControl IO m) | |
=> (Throws e => m a) -> (e -> m a) -> m a | |
catchCheckedL act = Lifted.catch (unthrow (Proxy :: Proxy e) act) | |
{------------------------------------------------------------------------------- | |
Auxiliary definitions (not exported) | |
-------------------------------------------------------------------------------} | |
newtype Wrap e a = Wrap { unWrap :: Throws e => a } | |
coerceWrap :: Wrap e a -> Wrap (Catch e) a | |
#if __GLASGOW_HASKELL__ >= 708 | |
coerceWrap = coerce | |
#else | |
coerceWrap = unsafeCoerce | |
#endif | |
data Proxy a = Proxy | |
newtype Catch a = Catch a | |
instance Throws (Catch e) where |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment