in-other-words-0.1.0.0: src/Control/Effect/Fail.hs
{-# LANGUAGE BlockArguments, DerivingVia #-}
module Control.Effect.Fail
( -- * Effects
Fail(..)
-- * Interpretations
, runFail
, failToThrow
, failToNonDet
, failToAlt
-- * Simple variants of interpretations
, failToThrowSimple
-- * Threading constraints
, ErrorThreads
-- * Carriers
, FailC
, InterpretFailC(..)
, InterpretFailReifiedC
, FailToNonDetC
, FailToAltC
, InterpretFailSimpleC(..)
) where
import Data.Coerce
import Control.Applicative
import Control.Monad
import qualified Control.Monad.Fail as Fail
import Control.Effect
import Control.Effect.Error
import Control.Effect.NonDet
import Control.Effect.Type.Alt
import Control.Effect.Type.Fail
import Control.Effect.Carrier
-- Imports for coercion
import Control.Effect.Internal.Utils
import Control.Effect.Carrier.Internal.Interpret
import Control.Effect.Carrier.Internal.Intro
import Control.Effect.Carrier.Internal.Compose
import Control.Monad.Trans.Identity
-- | Like 'InterpretC' specialized to interpret 'Fail', but with a 'MonadFail'
-- instance based on the interpreted 'Fail'.
newtype InterpretFailC h m a = InterpretFailC {
unInterpretFailC :: InterpretC h Fail m a
}
deriving ( Functor, Applicative, Monad
, Alternative, MonadPlus
, MonadFix, MonadIO
, MonadThrow, MonadCatch, MonadMask
, MonadBase b, MonadBaseControl b
)
deriving (MonadTrans, MonadTransControl) via IdentityT
type InterpretFailReifiedC m a
= forall s
. ReifiesHandler s Fail m
=> InterpretFailC (ViaReifiedH s) m a
deriving via InterpretC h Fail m instance
Handler h Fail m => Carrier (InterpretFailC h m)
deriving via Effly (InterpretFailC h m)
instance Handler h Fail m
=> MonadFail (InterpretFailC h m)
-- | Transform a 'Fail' effect to a 'Throw' effect by providing a function
-- to transform a pattern match failure into an exception.
--
-- You can use this in application code to locally get access to a 'MonadFail'
-- instance (since 'InterpretFailReifiedC' has a 'MonadFail' instance based
-- on the 'Fail' effect this interprets).
--
-- For example:
--
-- @
-- 'failToThrow' (\_ -> 'throw' exc) (do { Just a <- pure Nothing; return a})
-- = 'throw' exc
-- @
--
-- This has a higher-rank type, as it makes use of 'InterpretFailReifiedC'.
-- __This makes 'failToThrow' very difficult to use partially applied.__
-- __In particular, it can't be composed using @'.'@.__
--
-- If performance is secondary, consider using the slower 'failToThrowSimple',
-- which doesn't have a higher-rank type. __However__, you typically don't
-- want to use 'failToThrowSimple' in application code, since 'failToThrowSimple'
-- emits a 'ReaderThreads' threading constraint (see 'Threaders').
failToThrow :: Eff (Throw e) m
=> (String -> e)
-> InterpretFailReifiedC m a
-> m a
failToThrow f m =
interpret \case
Fail s -> throw (f s)
$ unInterpretFailC
$ m
{-# INLINE failToThrow #-}
data FailToAltH
type FailToAltC = InterpretFailC FailToAltH
instance Eff Alt m => Handler FailToAltH Fail m where
effHandler _ = runEffly empty
{-# INLINEABLE effHandler #-}
data FailToNonDetH
instance Eff NonDet m => Handler FailToNonDetH Fail m where
effHandler _ = lose
{-# INLINEABLE effHandler #-}
type FailToNonDetC = InterpretFailC FailToNonDetH
-- | Transform a 'Fail' effect to an 'Alt' effect by having a
-- pattern match failure be 'empty'.
--
-- You can use this in application code to locally get access to a 'MonadFail'
-- instance (since 'FailToAltC' has a 'MonadFail' instance based
-- on the 'Fail' effect this interprets).
failToAlt :: Eff Alt m
=> FailToAltC m a
-> m a
failToAlt = interpretViaHandler .# unInterpretFailC
{-# INLINE failToAlt #-}
-- | Transform a 'Fail' effect to a 'NonDet' effect by having a
-- pattern match failure be 'lose'.
--
-- You can use this in application code to locally get access to a 'MonadFail'
-- instance (since 'FailToNonDetC' has a 'MonadFail' instance based
-- on the 'Fail' effect this interprets).
--
-- For example:
--
-- @
-- 'failToNonDet' (do { Just a <- pure Nothing; return a})
-- = 'lose'
-- @
failToNonDet :: Eff NonDet m
=> FailToNonDetC m a
-> m a
failToNonDet = interpretViaHandler .# unInterpretFailC
{-# INLINE failToNonDet #-}
data FailH
type FailC = CompositionC
'[ ReinterpretC FailH Fail '[Throw String]
, ThrowC String
]
instance Eff (Throw String) m
=> Handler FailH Fail m where
effHandler = throw @String .# coerce
{-# INLINEABLE effHandler #-}
-- | Run a 'Fail' effect purely, by returning @Left failureMessage@
-- upon a pattern match failure.
--
-- 'FailC' has an 'Alternative' instance based on the 'Alt'
-- effect it interprets.
runFail :: forall m a p
. ( Threaders '[ErrorThreads] m p
, Carrier m
)
=> FailC m a
-> m (Either String a)
runFail =
runThrow
.# reinterpretViaHandler
.# runComposition
-- | Like 'InterpretSimpleC' specialized to interpret 'Fail', but with
-- a 'MonadFail' instance based on the interpreted 'Fail'.
newtype InterpretFailSimpleC m a = InterpretFailSimpleC {
unInterpretFailSimpleC :: InterpretSimpleC Fail m a
}
deriving ( Functor, Applicative, Monad
, Alternative, MonadPlus
, MonadFix, MonadIO
, MonadThrow, MonadCatch, MonadMask
, MonadBase b, MonadBaseControl b
)
deriving MonadTrans
deriving newtype instance
(Monad m, Carrier (InterpretSimpleC Fail m))
=> Carrier (InterpretFailSimpleC m)
instance (Monad m, Carrier (InterpretSimpleC Fail m))
=> Fail.MonadFail (InterpretFailSimpleC m) where
fail = send .# Fail
{-# INLINE fail #-}
-- | Transform a 'Fail' effect to a 'Throw' effect by providing a function
-- to transform a pattern match failure into an exception.
--
-- This is a less performant version of 'failToThrow' that doesn't have
-- a higher-rank type, making it much easier to use partially applied.
--
-- Unlike 'failToThrow', __you typically don't want to use this in__
-- __application code__, since this emits a 'ReaderThreads'
-- threading constraint (see 'Threaders').
failToThrowSimple :: forall e m a p
. ( Eff (Throw e) m
, Threaders '[ReaderThreads] m p
)
=> (String -> e)
-> InterpretFailSimpleC m a
-> m a
failToThrowSimple f =
interpretSimple \case
Fail s -> throw (f s)
.# unInterpretFailSimpleC
{-# INLINE failToThrowSimple #-}