@@ -17,6 +17,10 @@ module Effectful.Internal.Monad
1717 , unsafeEff_
1818 , unsafeUnliftEff
1919
20+ -- * Fail
21+ , Fail
22+ , runFail
23+
2024 -- * IO
2125 , IOE
2226 , runIOE
@@ -45,6 +49,7 @@ import Control.Monad.Fix
4549import Control.Monad.IO.Class
4650import Control.Monad.IO.Unlift
4751import Control.Monad.Trans.Control
52+import Data.Unique
4853import GHC.Magic (oneShot)
4954import System.IO.Unsafe (unsafeDupablePerformIO)
5055
@@ -172,6 +177,43 @@ instance MonadMask (Eff es) where
172177 c <- unEff (release resource $ ExitCaseSuccess b) es
173178 pure (b, c)
174179
180+----------------------------------------
181+-- Fail
182+
183+data Fail :: Effect where
184+ Fail :: Unique -> Fail m r
185+
186+runFail :: Eff (Fail : es) a -> Eff es (Either String a)
187+runFail m = unsafeEff $ \es0 -> mask $ \release -> do
188+ -- A unique tag is picked so that different runFail handlers don't catch each
189+ -- other's exceptions.
190+ tag <- newUnique
191+ size0 <- sizeEnv es0
192+ es <- unsafeConsEnv (IdE (Fail tag)) noRelinker es0
193+ r <- tryFailIO tag (release $ unEff m es) `onException` unsafeTailEnv size0 es
194+ _ <- unsafeTailEnv size0 es
195+ pure r
196+
197+instance Fail :> es => MonadFail (Eff es) where
198+ fail msg = readerEffectM $ \(IdE (Fail tag)) -> unsafeEff_ $ do
199+ throwM $ FailEx tag msg
200+
201+--------------------
202+
203+data FailEx = FailEx Unique String
204+instance Show FailEx where
205+ showsPrec p (FailEx _ msg)
206+ = ("Effectful.Internal.Monad.FailEx " ++)
207+ . showsPrec p msg
208+instance Exception FailEx
209+
210+tryFailIO :: Unique -> IO a -> IO (Either String a)
211+tryFailIO tag m =
212+ (Right <$> m) `catch` \err@(FailEx etag msg) -> do
213+ if tag == etag
214+ then pure $ Left msg
215+ else throwM err
216+
175217----------------------------------------
176218-- IO
177219