in-other-words-0.1.0.0: src/Control/Effect/Alt.hs
{-# LANGUAGE BlockArguments, DerivingVia #-}
module Control.Effect.Alt
( -- * Effects
Alt(..)
, Alternative(..)
-- * Interpretations
, runAltMaybe
, altToError
, altToNonDet
-- * Simple variants of interpretations
, altToErrorSimple
-- * Threading constraints
, ErrorThreads
-- * Carriers
, AltMaybeC
, InterpretAltC(..)
, InterpretAltReifiedC
, AltToNonDetC
, InterpretAltSimpleC(..)
) where
import Control.Applicative
import Control.Monad
import Control.Effect
import Control.Effect.Carrier
import Control.Effect.Error
import Control.Effect.NonDet
import Control.Effect.Type.Alt
-- For coercion purposes
import Control.Effect.Internal.Utils
import Control.Effect.Internal.Error
import Control.Effect.Carrier.Internal.Compose
import Control.Effect.Carrier.Internal.Interpret
import Control.Effect.Carrier.Internal.Intro
import Control.Monad.Trans.Except
import Control.Monad.Trans.Identity
-- | Like InterpretC specialized to interpret 'Alt', but has 'Alternative' and
-- 'MonadPlus' instances based on the interpreted 'Alt'.
newtype InterpretAltC h m a = InterpretAltC {
unInterpretAltC :: InterpretC h Alt m a
}
deriving ( Functor, Applicative, Monad
, MonadFix, MonadIO, MonadFail
, MonadThrow, MonadCatch, MonadMask
, MonadBase b, MonadBaseControl b
)
deriving (MonadTrans, MonadTransControl)
-- Like InterpretSimpleC specialized to interpret 'Alt', but has 'Alternative' and
-- 'MonadPlus' instances based on the interpreted 'Alt'.
newtype InterpretAltSimpleC m a = InterpretAltSimpleC {
unInterpretAltSimpleC :: InterpretSimpleC Alt m a
}
deriving ( Functor, Applicative, Monad
, MonadFix, MonadIO, MonadFail
, MonadThrow, MonadCatch, MonadMask
, MonadBase b, MonadBaseControl b
)
deriving MonadTrans
type InterpretAltReifiedC m a =
forall s
. ReifiesHandler s Alt m
=> InterpretAltC (ViaReifiedH s) m a
deriving newtype instance Handler h Alt m => Carrier (InterpretAltC h m)
deriving via Effly (InterpretAltC h m)
instance Handler h Alt m => Alternative (InterpretAltC h m)
instance Handler h Alt m => MonadPlus (InterpretAltC h m)
deriving newtype instance
(Monad m, Carrier (InterpretSimpleC Alt m))
=> Carrier (InterpretAltSimpleC m)
deriving via Effly (InterpretAltSimpleC m)
instance (Monad m, Carrier (InterpretSimpleC Alt m))
=> Alternative (InterpretAltSimpleC m)
instance (Monad m, Carrier (InterpretSimpleC Alt m))
=> MonadPlus (InterpretAltSimpleC m)
data AltToErrorUnitH
instance Eff (Error ()) m
=> Handler AltToErrorUnitH Alt m where
effHandler = \case
Empty -> throw ()
Alt ma mb -> ma `catch` \() -> mb
{-# INLINEABLE effHandler #-}
type AltMaybeC = CompositionC
'[ IntroUnderC Alt '[Catch (), Throw ()]
, InterpretAltC AltToErrorUnitH
, ErrorC ()
]
-- | Run an 'Alt' effect purely, returning @Nothing@ on an unhandled
-- 'empty'.
--
-- 'AltMaybeC' has an 'Alternative' instance based on the 'Alt'
-- effect it interprets.
--
-- @'Derivs' ('AltMaybeC' m) = 'Alt' ': 'Derivs' m@
-- @'Prims' ('AltMaybeC' m) = 'Control.Effect.Optional.Optional' ((->) ()) ': 'Prims' m@
runAltMaybe :: forall m a p
. ( Threaders '[ErrorThreads] m p
, Carrier m
)
=> AltMaybeC m a
-> m (Maybe a)
runAltMaybe =
fmap (either (const Nothing) Just)
.# runError
.# interpretViaHandler
.# unInterpretAltC
.# introUnder
.# runComposition
{-# INLINE runAltMaybe #-}
-- | Transform an 'Alt' effect into 'Error' by describing it in
-- terms of 'throw' and 'catch', using the provided exception to act as 'empty'.
--
-- You can use this in application code to locally get access to an 'Alternative'
-- instance (since 'InterpretAltReifiedC' has an 'Alternative' instance based
-- on the 'Alt' effect this interprets).
--
-- For example:
--
-- @
-- 'altToError' ('throw' exc) 'empty' = 'throw' exc
-- @
--
-- 'altToError' has a higher-rank type, as it makes use of 'InterpretAltReifiedC'.
-- __This makes 'altToError' very difficult to use partially applied.__
-- __In particular, it can't be composed using @'.'@.__
--
-- If performance is secondary, consider using the slower 'altToErrorSimple',
-- which doesn't have a higher-rank type. __However__, you typically don't
-- want to use 'altToErrorSimple' in application code, since 'altToErrorSimple'
-- emits a 'ReaderThreads' threading constraint (see 'Threaders').
altToError :: forall e m a
. Eff (Error e) m
=> e
-> InterpretAltReifiedC m a
-> m a
altToError e m =
interpret \case
Empty -> throw e
Alt ma mb -> ma `catch` \(_ :: e) -> mb
$ unInterpretAltC
$ m
{-# INLINE altToError #-}
data AltToNonDetH
instance Eff NonDet m => Handler AltToNonDetH Alt m where
effHandler = \case
Empty -> lose
Alt ma mb -> choose ma mb
{-# INLINEABLE effHandler #-}
type AltToNonDetC = InterpretAltC AltToNonDetH
-- | Transform an 'Alt' effect into 'NonDet' by describing it
-- in terms of 'lose' and 'choose'.
--
-- You can use this in application code to locally get access to an 'Alternative'
-- instance (since 'AltToNonDetC' has an 'Alternative' instance based
-- on the 'Alt' effect this interprets).
--
-- For example:
--
-- @
-- 'altToNonDet' 'empty' = 'lose'
-- @
altToNonDet :: Eff NonDet m
=> AltToNonDetC m a
-> m a
altToNonDet = interpretViaHandler .# unInterpretAltC
{-# INLINE altToNonDet #-}
-- | Transform an 'Alt' in terms of 'throw' and 'catch', by providing an
-- exception to act as 'empty'.
--
-- This is a less performant version of 'altToError' that doesn't have
-- a higher-rank type, making it much easier to use partially applied.
--
-- Unlike 'altToError', __you typically don't want to use this in__
-- __application code__, since this emits a 'ReaderThreads'
-- threading constraint (see 'Threaders').
altToErrorSimple :: forall e m a p
. ( Eff (Error e) m
, Threaders '[ReaderThreads] m p
)
=> e
-> InterpretAltSimpleC m a
-> m a
altToErrorSimple e =
interpretSimple \case
Empty -> throw e
Alt ma mb -> ma `catch` \(_ :: e) -> mb
.# unInterpretAltSimpleC
{-# INLINE altToErrorSimple #-}