fused-effects-1.0.0.1: src/Control/Algebra.hs
{-# LANGUAGE CPP, ConstraintKinds, DeriveFunctor, FlexibleInstances, FunctionalDependencies, RankNTypes, TypeOperators, UndecidableInstances #-}
{- | The 'Algebra' class is the mechanism with which effects are interpreted.
An instance of the 'Algebra' class defines an interpretation of an effect signature atop a given monad.
@since 1.0.0.0
-}
module Control.Algebra
( Algebra(..)
, run
, Has
, send
-- * Re-exports
, (:+:) (..)
, module Control.Effect.Class
) where
import Control.Effect.Catch.Internal
import Control.Effect.Choose.Internal
import Control.Effect.Class
import Control.Effect.Empty.Internal
import Control.Effect.Error.Internal
import Control.Effect.Lift.Internal
import Control.Effect.NonDet.Internal
import Control.Effect.Reader.Internal
import Control.Effect.State.Internal
import Control.Effect.Sum ((:+:)(..), Member(..), Members)
import Control.Effect.Throw.Internal
import Control.Effect.Writer.Internal
import Control.Monad ((<=<))
import Data.Coerce
import Data.Functor.Identity
import qualified Control.Monad.Trans.Except as Except
import qualified Control.Monad.Trans.Identity as Identity
import qualified Control.Monad.Trans.Reader as Reader
import qualified Control.Monad.Trans.RWS.Lazy as RWS.Lazy
import qualified Control.Monad.Trans.RWS.Strict as RWS.Strict
import qualified Control.Monad.Trans.State.Lazy as State.Lazy
import qualified Control.Monad.Trans.State.Strict as State.Strict
import qualified Control.Monad.Trans.Writer.Lazy as Writer.Lazy
import qualified Control.Monad.Trans.Writer.Strict as Writer.Strict
import Data.List.NonEmpty (NonEmpty)
import Data.Monoid
import qualified Data.Semigroup as S
import Data.Tuple (swap)
-- | The class of carriers (results) for algebras (effect handlers) over signatures (effects), whose actions are given by the 'alg' method.
--
-- @since 1.0.0.0
class (HFunctor sig, Monad m) => Algebra sig m | m -> sig where
-- | Construct a value in the carrier for an effect signature (typically a sum of a handled effect and any remaining effects).
alg :: sig m a -> m a
-- | Run an action exhausted of effects to produce its final result value.
--
-- @since 1.0.0.0
run :: Identity a -> a
run = runIdentity
{-# INLINE run #-}
-- | @m@ is a carrier for @sig@ containing @eff@.
--
-- Note that if @eff@ is a sum, it will be decomposed into multiple 'Member' constraints. While this technically allows one to combine multiple unrelated effects into a single 'Has' constraint, doing so has two significant drawbacks:
--
-- 1. Due to [a problem with recursive type families](https://gitlab.haskell.org/ghc/ghc/issues/8095), this can lead to significantly slower compiles.
--
-- 2. It defeats @ghc@’s warnings for redundant constraints, and thus can lead to a proliferation of redundant constraints as code is changed.
type Has eff sig m = (Members eff sig, Algebra sig m)
-- | Construct a request for an effect to be interpreted by some handler later on.
send :: (Member eff sig, Algebra sig m) => eff m a -> m a
send = alg . inj
{-# INLINE send #-}
-- base
instance Algebra (Lift IO) IO where
alg (LiftWith with k) = with (Identity ()) coerce >>= k . runIdentity
instance Algebra (Lift Identity) Identity where
alg (LiftWith with k) = with (Identity ()) coerce >>= k . runIdentity
instance Algebra Choose NonEmpty where
alg (Choose m) = m True S.<> m False
instance Algebra Empty Maybe where
alg Empty = Nothing
instance Algebra (Error e) (Either e) where
alg (L (Throw e)) = Left e
alg (R (Catch m h k)) = either (k <=< h) k m
instance Algebra (Reader r) ((->) r) where
alg (Ask k) r = k r r
alg (Local f m k) r = k (m (f r)) r
instance Algebra NonDet [] where
alg (L Empty) = []
alg (R (Choose k)) = k True ++ k False
instance Monoid w => Algebra (Writer w) ((,) w) where
alg (Tell w (w', k)) = (mappend w w', k)
alg (Listen (w, a) k) = let (w', a') = k w a in (mappend w w', a')
alg (Censor f (w, a) k) = let (w', a') = k a in (mappend (f w) w', a')
-- transformers
instance (Algebra sig m, Effect sig) => Algebra (Error e :+: sig) (Except.ExceptT e m) where
alg (L (L (Throw e))) = Except.throwE e
alg (L (R (Catch m h k))) = Except.catchE m h >>= k
alg (R other) = Except.ExceptT $ alg (thread (Right ()) (either (pure . Left) Except.runExceptT) other)
instance Algebra sig m => Algebra sig (Identity.IdentityT m) where
alg = Identity.IdentityT . alg . handleCoercible
#if MIN_VERSION_base(4,12,0)
-- | This instance permits effectful actions to be lifted into the 'Ap' monad
-- given a monoidal return type, which can provide clarity when chaining calls
-- to 'mappend'.
--
-- > mappend <$> act1 <*> (mappend <$> act2 <*> act3)
--
-- is equivalent to
--
-- > getAp (act1 <> act2 <> act3)
--
-- @since 1.0.1.0
instance Algebra sig m => Algebra sig (Ap m) where
alg = Ap . alg . handleCoercible
#endif
-- | This instance permits effectful actions to be lifted into the 'Alt' monad,
-- which eases the invocation of repeated alternation with '<|>':
--
-- > a <|> b <|> c <|> d
--
-- is equivalent to
--
-- > getAlt (mconcat [a, b, c, d])
--
-- @since 1.0.1.0
instance Algebra sig m => Algebra sig (Alt m) where
alg = Alt . alg . handleCoercible
instance Algebra sig m => Algebra (Reader r :+: sig) (Reader.ReaderT r m) where
alg (L (Ask k)) = Reader.ask >>= k
alg (L (Local f m k)) = Reader.local f m >>= k
alg (R other) = Reader.ReaderT $ \ r -> alg (hmap (flip Reader.runReaderT r) other)
newtype RWSTF w s a = RWSTF { unRWSTF :: (a, s, w) }
deriving (Functor)
toRWSTF :: Monoid w => w -> (a, s, w) -> RWSTF w s a
toRWSTF w (a, s, w') = RWSTF (a, s, mappend w w')
{-# INLINE toRWSTF #-}
instance (Algebra sig m, Effect sig, Monoid w) => Algebra (Reader r :+: Writer w :+: State s :+: sig) (RWS.Lazy.RWST r w s m) where
alg (L (Ask k)) = RWS.Lazy.ask >>= k
alg (L (Local f m k)) = RWS.Lazy.local f m >>= k
alg (R (L (Tell w k))) = RWS.Lazy.tell w *> k
alg (R (L (Listen m k))) = RWS.Lazy.listen m >>= uncurry (flip k)
alg (R (L (Censor f m k))) = RWS.Lazy.censor f m >>= k
alg (R (R (L (Get k)))) = RWS.Lazy.get >>= k
alg (R (R (L (Put s k)))) = RWS.Lazy.put s *> k
alg (R (R (R other))) = RWS.Lazy.RWST $ \ r s -> unRWSTF <$> alg (thread (RWSTF ((), s, mempty)) (\ (RWSTF (x, s, w)) -> toRWSTF w <$> RWS.Lazy.runRWST x r s) other)
instance (Algebra sig m, Effect sig, Monoid w) => Algebra (Reader r :+: Writer w :+: State s :+: sig) (RWS.Strict.RWST r w s m) where
alg (L (Ask k)) = RWS.Strict.ask >>= k
alg (L (Local f m k)) = RWS.Strict.local f m >>= k
alg (R (L (Tell w k))) = RWS.Strict.tell w *> k
alg (R (L (Listen m k))) = RWS.Strict.listen m >>= uncurry (flip k)
alg (R (L (Censor f m k))) = RWS.Strict.censor f m >>= k
alg (R (R (L (Get k)))) = RWS.Strict.get >>= k
alg (R (R (L (Put s k)))) = RWS.Strict.put s *> k
alg (R (R (R other))) = RWS.Strict.RWST $ \ r s -> unRWSTF <$> alg (thread (RWSTF ((), s, mempty)) (\ (RWSTF (x, s, w)) -> toRWSTF w <$> RWS.Strict.runRWST x r s) other)
instance (Algebra sig m, Effect sig) => Algebra (State s :+: sig) (State.Lazy.StateT s m) where
alg (L (Get k)) = State.Lazy.get >>= k
alg (L (Put s k)) = State.Lazy.put s *> k
alg (R other) = State.Lazy.StateT $ \ s -> swap <$> alg (thread (s, ()) (\ (s, x) -> swap <$> State.Lazy.runStateT x s) other)
instance (Algebra sig m, Effect sig) => Algebra (State s :+: sig) (State.Strict.StateT s m) where
alg (L (Get k)) = State.Strict.get >>= k
alg (L (Put s k)) = State.Strict.put s *> k
alg (R other) = State.Strict.StateT $ \ s -> swap <$> alg (thread (s, ()) (\ (s, x) -> swap <$> State.Strict.runStateT x s) other)
instance (Algebra sig m, Effect sig, Monoid w) => Algebra (Writer w :+: sig) (Writer.Lazy.WriterT w m) where
alg (L (Tell w k)) = Writer.Lazy.tell w *> k
alg (L (Listen m k)) = Writer.Lazy.listen m >>= uncurry (flip k)
alg (L (Censor f m k)) = Writer.Lazy.censor f m >>= k
alg (R other) = Writer.Lazy.WriterT $ swap <$> alg (thread (mempty, ()) (\ (s, x) -> swap . fmap (mappend s) <$> Writer.Lazy.runWriterT x) other)
instance (Algebra sig m, Effect sig, Monoid w) => Algebra (Writer w :+: sig) (Writer.Strict.WriterT w m) where
alg (L (Tell w k)) = Writer.Strict.tell w *> k
alg (L (Listen m k)) = Writer.Strict.listen m >>= uncurry (flip k)
alg (L (Censor f m k)) = Writer.Strict.censor f m >>= k
alg (R other) = Writer.Strict.WriterT $ swap <$> alg (thread (mempty, ()) (\ (s, x) -> swap . fmap (mappend s) <$> Writer.Strict.runWriterT x) other)