{-# LANGUAGE TypeOperators #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE CPP #-} -- | Exception-producing and exception-handling effects module Control.Eff.Exception( Exc(..) , Fail , throwExc , die , runExc , runFail , catchExc , onFail , rethrowExc , liftEither , liftEitherM , liftMaybe , ignoreFail ) where import Control.Monad (void) import Data.Typeable import Control.Eff import Control.Eff.Lift #if MIN_VERSION_base(4,7,0) #define Typeable1 Typeable #endif -- | These are exceptions of the type e. This is akin to the error monad. newtype Exc e v = Exc e deriving (Functor, Typeable) type Fail = Exc () -- | Throw an exception in an effectful computation. throwExc :: (Typeable e, Member (Exc e) r) => e -> Eff r a throwExc e = send (\_ -> inj $ Exc e) {-# INLINE throwExc #-} -- | Makes an effect fail, preventing future effects from happening. die :: Member Fail r => Eff r a die = throwExc () {-# INLINE die #-} -- | Run a computation that might produce an exception. runExc :: Typeable e => Eff (Exc e :> r) a -> Eff r (Either e a) runExc = loop . admin where loop (Val x) = return (Right x) loop (E u) = handleRelay u loop (\(Exc e) -> return (Left e)) -- | Runs a failable effect, such that failed computation return 'Nothing', and -- 'Just' the return value on success. runFail :: Eff (Fail :> r) a -> Eff r (Maybe a) runFail = fmap (either (\_-> Nothing) Just) . runExc {-# INLINE runFail #-} -- | Run a computation that might produce exceptions, -- and give it a way to deal with the exceptions that come up. catchExc :: (Typeable e, Member (Exc e) r) => Eff r a -> (e -> Eff r a) -> Eff r a catchExc m handle = loop (admin m) where loop (Val x) = return x loop (E u) = interpose u loop (\(Exc e) -> handle e) -- | Add a default value (i.e. failure handler) to a fallible computation. -- This hides the fact that a failure happened. onFail :: Eff (Fail :> r) a -- ^ The fallible computation. -> Eff r a -- ^ The computation to run on failure. -> Eff r a onFail e handle = runFail e >>= maybe handle return {-# INLINE onFail #-} -- | Run a computation until it produces an exception, -- and convert and throw that exception in a new context. rethrowExc :: (Typeable e, Typeable e', Member (Exc e') r) => (e -> e') -> Eff (Exc e :> r) a -> Eff r a rethrowExc t eff = runExc eff >>= either (throwExc . t) return -- | Treat Lefts as exceptions and Rights as return values. liftEither :: (Typeable e, Member (Exc e) r) => Either e a -> Eff r a liftEither (Left e) = throwExc e liftEither (Right a) = return a {-# INLINE liftEither #-} -- | `liftEither` in a lifted Monad liftEitherM :: (Typeable1 m, Typeable e, Member (Exc e) r, Member (Lift m) r) => m (Either e a) -> Eff r a liftEitherM m = lift m >>= liftEither {-# INLINE liftEitherM #-} -- | Lift a maybe into the 'Fail' effect, causing failure if it's 'Nothing'. liftMaybe :: Member Fail r => Maybe a -> Eff r a liftMaybe = maybe die return {-# INLINE liftMaybe #-} -- | Ignores a failure event. Since the event can fail, you cannot inspect its -- return type, because it has none on failure. To inspect it, use 'runFail'. ignoreFail :: Eff (Fail :> r) a -> Eff r () ignoreFail e = void e `onFail` return () {-# INLINE ignoreFail #-}