rescue-0.4.0: library/Control/Monad/Rescue/Class.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
-- | The 'MonadRescue' class, meant for retrieving the success/failure branches
module Control.Monad.Rescue.Class (MonadRescue (..)) where
import Data.WorldPeace
import Control.Exception
import qualified Control.Monad.Catch as Catch
import Control.Monad.Cont
import Control.Monad.Raise
import Control.Monad.Trans.Except
import Control.Monad.Trans.Identity
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Reader
import qualified Control.Monad.RWS.Lazy as Lazy
import qualified Control.Monad.RWS.Strict as Strict
import qualified Control.Monad.State.Lazy as Lazy
import qualified Control.Monad.State.Strict as Strict
import qualified Control.Monad.Writer.Lazy as Lazy
import qualified Control.Monad.Writer.Strict as Strict
-- $setup
--
-- >>> :set -XDataKinds
-- >>> :set -XFlexibleContexts
-- >>> :set -XTypeApplications
-- >>> :set -XLambdaCase
--
-- >>> import Control.Monad.Trans.Rescue
-- >>> import Data.Functor.Identity
-- >>> import Data.Proxy
-- >>> import Data.WorldPeace as OpenUnion
--
-- >>> data FooErr = FooErr deriving Show
-- >>> data BarErr = BarErr deriving Show
-- >>> data QuuxErr = QuuxErr deriving Show
-- | Pull a potential error out of the surrounding context
-- NOTE that the target `m` may not even be aware of Raise/Rescue. It's an escape to the "normal" world
class MonadRaise m => MonadRescue m where
-- | Attempt some action, exposing the success and error branches
--
-- ==== __Examples__
--
-- >>> :{
-- goesBoom :: Int -> Rescue '[FooErr, BarErr] Int
-- goesBoom x =
-- if x > 50
-- then return x
-- else raise FooErr
-- :}
--
-- >>> runRescue . attempt $ goesBoom 42
-- Right (Left (Identity FooErr))
--
-- Where @Identity fooErr@ is the selection of the 'OpenUnion'.
-- In practice you would handle the 'OpenUnion' like so:
--
-- >>> let handleErr = catchesOpenUnion (show, show)
-- >>> let x = attempt (goesBoom 42) >>= pure . either handleErr show
-- >>> runRescue x
-- Right "FooErr"
--
-- Where @Identity FooErr@ is the selection of the 'OpenUnion'.
attempt :: m a -> m (Either (ErrorCase m) a)
instance MonadRescue Maybe where
attempt Nothing = Just . Left $ openUnionLift ()
attempt (Just x) = Just $ Right x
instance MonadRescue [] where
attempt [] = [Left $ include ()]
attempt xs = Right <$> xs
instance MonadRescue (Either (OpenUnion errs)) where
attempt action = Right action
instance MonadRescue IO where
attempt action =
Catch.try action >>= \case
Left (err :: IOException) -> return . Left $ include err
Right val -> return $ Right val
instance
( MonadRescue m
, () `IsMember` Errors m
, Errors m `Contains` Errors m
)
=> MonadRescue (MaybeT m) where
attempt (MaybeT action) =
MaybeT $
attempt action >>= \case
Left errs -> return . Just . Left $ include errs
Right Nothing -> return . Just . Left $ include ()
Right (Just val) -> return . Just $ Right val
instance MonadRescue m => MonadRescue (IdentityT m) where
attempt (IdentityT action) = IdentityT $ attempt action
instance
( MonadRescue m
, Contains (Errors m) errs
)
=> MonadRescue (ExceptT (OpenUnion errs) m) where
attempt (ExceptT action) =
lift $
attempt action >>= \case
Left err -> return . Left $ include err
Right errOrVal -> return errOrVal
instance MonadRescue m => MonadRescue (ReaderT cfg m) where
attempt = mapReaderT attempt
instance (Monoid w, MonadRescue m) => MonadRescue (Lazy.WriterT w m) where
attempt = Lazy.mapWriterT runner2
instance (Monoid w, MonadRescue m) => MonadRescue (Strict.WriterT w m) where
attempt = Strict.mapWriterT runner2
instance MonadRescue m => MonadRescue (Lazy.StateT s m) where
attempt = Lazy.mapStateT runner2
instance MonadRescue m => MonadRescue (Strict.StateT s m) where
attempt = Strict.mapStateT runner2
instance (Monoid w, MonadRescue m) => MonadRescue (Lazy.RWST r w s m) where
attempt = Lazy.mapRWST runner3
instance (Monoid w, MonadRescue m) => MonadRescue (Strict.RWST r w s m) where
attempt = Strict.mapRWST runner3
instance MonadRescue m => MonadRescue (ContT r m) where
attempt = withContT $ \b_mr current -> b_mr =<< attempt (pure current)
runner2
:: ( MonadRescue m
, RaisesOnly m errs
)
=> m (a, w)
-> m (Either (OpenUnion errs) a, w)
runner2 inner = do
(a, w) <- inner
errOrVal <- attempt (pure a)
return (errOrVal, w)
runner3
:: ( MonadRescue m
, RaisesOnly m errs
)
=> m (a, b, c)
-> m (Either (OpenUnion errs) a, b, c)
runner3 inner = do
(a, s, w) <- inner
errOrVal <- attempt (pure a)
return (errOrVal, s, w)