Add the Fail effect

This commit is contained in:
Andrzej Rybczak 2021-06-30 19:01:39 +02:00
parent acaf81096f
commit fc8a1962ff
4 changed files with 57 additions and 9 deletions

View File

@ -71,6 +71,7 @@ library
Effectful.Class.State
Effectful.Class.Writer
Effectful.Error
Effectful.Fail
Effectful.Internal.Effect
Effectful.Internal.Env
Effectful.Internal.Monad

View File

@ -65,7 +65,7 @@ throwError
=> e
-> Eff es a
throwError e = readerEffectM @(Error e) $ \(IdE (Error tag)) -> unsafeEff_ $ do
throwIO $ WrapErr tag callStack e
throwIO $ ErrorEx tag callStack e
catchError
:: forall e es a. (Typeable e, Error e :> es)
@ -88,19 +88,18 @@ tryError m = (Right <$> m) `catchError` \es e -> pure $ Left (es, e)
----------------------------------------
-- Helpers
data WrapErr e = WrapErr Unique CallStack e
instance Typeable e => Show (WrapErr e) where
showsPrec p (WrapErr _ cs e)
= showsPrec p "Effectful.Error.WrapErr ("
data ErrorEx e = ErrorEx Unique CallStack e
instance Typeable e => Show (ErrorEx e) where
showsPrec p (ErrorEx _ cs e)
= ("Effectful.Error.ErrorEx (" ++)
. showsPrec p (typeOf e)
. showsPrec p ") "
. (") " ++)
. showsPrec p cs
instance Typeable e => Exception (WrapErr e)
instance Typeable e => Exception (ErrorEx e)
catchErrorIO :: Typeable e => Unique -> IO a -> (CallStack -> e -> IO a) -> IO a
catchErrorIO tag m handler = do
m `catch` \err@(WrapErr etag e cs) -> do
m `catch` \err@(ErrorEx etag e cs) -> do
if tag == etag
then handler e cs
else throwIO err

6
src/Effectful/Fail.hs Normal file
View File

@ -0,0 +1,6 @@
module Effectful.Fail
( Fail
, runFail
) where
import Effectful.Internal.Monad

View File

@ -17,6 +17,10 @@ module Effectful.Internal.Monad
, unsafeEff_
, unsafeUnliftEff
-- * Fail
, Fail
, runFail
-- * IO
, IOE
, runIOE
@ -45,6 +49,7 @@ import Control.Monad.Fix
import Control.Monad.IO.Class
import Control.Monad.IO.Unlift
import Control.Monad.Trans.Control
import Data.Unique
import GHC.Magic (oneShot)
import System.IO.Unsafe (unsafeDupablePerformIO)
@ -172,6 +177,43 @@ instance MonadMask (Eff es) where
c <- unEff (release resource $ ExitCaseSuccess b) es
pure (b, c)
----------------------------------------
-- Fail
data Fail :: Effect where
Fail :: Unique -> Fail m r
runFail :: Eff (Fail : es) a -> Eff es (Either String a)
runFail m = unsafeEff $ \es0 -> mask $ \release -> do
-- A unique tag is picked so that different runFail handlers don't catch each
-- other's exceptions.
tag <- newUnique
size0 <- sizeEnv es0
es <- unsafeConsEnv (IdE (Fail tag)) noRelinker es0
r <- tryFailIO tag (release $ unEff m es) `onException` unsafeTailEnv size0 es
_ <- unsafeTailEnv size0 es
pure r
instance Fail :> es => MonadFail (Eff es) where
fail msg = readerEffectM $ \(IdE (Fail tag)) -> unsafeEff_ $ do
throwM $ FailEx tag msg
--------------------
data FailEx = FailEx Unique String
instance Show FailEx where
showsPrec p (FailEx _ msg)
= ("Effectful.Internal.Monad.FailEx " ++)
. showsPrec p msg
instance Exception FailEx
tryFailIO :: Unique -> IO a -> IO (Either String a)
tryFailIO tag m =
(Right <$> m) `catch` \err@(FailEx etag msg) -> do
if tag == etag
then pure $ Left msg
else throwM err
----------------------------------------
-- IO