{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MonoLocalBinds #-} {-# LANGUAGE UndecidableInstances #-} module Polysemy.Internal ( Semantic (..) , Member , send , sendM , run , runM , raise , Lift (..) , usingSemantic , liftSemantic , hoistSemantic , (.@) , (.@@) ) where import Control.Applicative import Control.Monad.Fix import Control.Monad.IO.Class import Data.Functor.Identity import Polysemy.Internal.Effect import Polysemy.Internal.Fixpoint import Polysemy.Internal.Lift import Polysemy.Internal.NonDet import Polysemy.Internal.Union ------------------------------------------------------------------------------ -- | The 'Semantic' monad handles computations of arbitrary extensible effects. -- A value of type @Semantic r@ describes a program with the capabilities of -- @r@. For best results, @r@ should always be kept polymorphic, but you can -- add capabilities via the 'Member' constraint. -- -- The value of the 'Semantic' monad is that it allows you to write programs -- against a set of effects without a predefined meaning, and provide that -- meaning later. For example, unlike with mtl, you can decide to interpret an -- 'Polysemy.Error.Error' effect tradtionally as an 'Either', or instead -- significantly faster as an 'IO' 'Control.Exception.Exception'. These -- interpretations (and others that you might add) may be used interchangably -- without needing to write any newtypes or 'Monad' instances. The only -- change needed to swap interpretations is to change a call from -- 'Polysemy.Error.runError' to 'Polysemy.Error.runErrorInIO'. -- -- The effect stack @r@ can contain arbitrary other monads inside of it. These -- monads are lifted into effects via the 'Lift' effect. Monadic values can be -- lifted into a 'Semantic' via 'sendM'. -- -- A 'Semantic' can be interpreted as a pure value (via 'run') or as any -- traditional 'Monad' (via 'runM'). Each effect @E@ comes equipped with some -- interpreters of the form: -- -- @ -- runE :: 'Semantic' (E ': r) a -> 'Semantic' r a -- @ -- -- which is responsible for removing the effect @E@ from the effect stack. It -- is the order in which you call the interpreters that determines the -- monomorphic representation of the @r@ parameter. -- -- After all of your effects are handled, you'll be left with either -- a @'Semantic' '[] a@ or a @'Semantic' '[ 'Lift' m ] a@ value, which can be -- consumed respectively by 'run' and 'runM'. -- -- ==== Examples -- -- As an example of keeping @r@ polymorphic, we can consider the type -- -- @ -- 'Member' ('Polysemy.State' String) r => 'Semantic' r () -- @ -- -- to be a program with access to -- -- @ -- 'Polysemy.State.get' :: 'Semantic' r String -- 'Polysemy.State.put' :: String -> 'Semantic' r () -- @ -- -- methods. -- -- By also adding a -- -- @ -- 'Member' ('Polysemy.Error' Bool) r -- @ -- -- constraint on @r@, we gain access to the -- -- @ -- 'Polysemy.Error.throw' :: Bool -> 'Semantic' r a -- 'Polysemy.Error.catch' :: 'Semantic' r a -> (Bool -> 'Semantic' r a) -> 'Semantic' r a -- @ -- -- functions as well. -- -- In this sense, a @'Member' ('Polysemy.State.State' s) r@ constraint is -- analogous to mtl's @'Control.Monad.State.Class.MonadState' s m@ and should -- be thought of as such. However, /unlike/ mtl, a 'Semantic' monad may have -- an arbitrary number of the same effect. -- -- For example, we can write a 'Semantic' program which can output either -- 'Int's or 'Bool's: -- -- @ -- foo :: ( 'Member' ('Polysemy.Output.Output' Int) r -- , 'Member' ('Polysemy.Output.Output' Bool) r -- ) -- => 'Semantic' r () -- foo = do -- 'Polysemy.Output.output' @Int 5 -- 'Polysemy.Output.output' True -- @ -- -- Notice that we must use @-XTypeApplications@ to specify that we'd like to -- use the ('Polysemy.Output.Output' 'Int') effect. newtype Semantic r a = Semantic { runSemantic :: ∀ m . Monad m => (∀ x. Union r (Semantic r) x -> m x) -> m a } ------------------------------------------------------------------------------ -- | Like 'runSemantic' but flipped for better ergonomics sometimes. usingSemantic :: Monad m => (∀ x. Union r (Semantic r) x -> m x) -> Semantic r a -> m a usingSemantic k m = runSemantic m k {-# INLINE usingSemantic #-} instance Functor (Semantic f) where fmap f (Semantic m) = Semantic $ \k -> fmap f $ m k {-# INLINE fmap #-} instance Applicative (Semantic f) where pure a = Semantic $ const $ pure a {-# INLINE pure #-} Semantic f <*> Semantic a = Semantic $ \k -> f k <*> a k {-# INLINE (<*>) #-} instance Monad (Semantic f) where return = pure {-# INLINE return #-} Semantic ma >>= f = Semantic $ \k -> do z <- ma k runSemantic (f z) k {-# INLINE (>>=) #-} instance (Member NonDet r) => Alternative (Semantic r) where empty = send Empty {-# INLINE empty #-} a <|> b = do send (Choose id) >>= \case False -> a True -> b {-# INLINE (<|>) #-} ------------------------------------------------------------------------------ -- | This instance will only lift 'IO' actions. If you want to lift into some -- other 'MonadIO' type, use this instance, and handle it via the -- 'Polysemy.IO.runIO' interpretation. instance (Member (Lift IO) r) => MonadIO (Semantic r) where liftIO = sendM {-# INLINE liftIO #-} instance Member Fixpoint r => MonadFix (Semantic r) where mfix f = send $ Fixpoint f {-# INLINE mfix #-} liftSemantic :: Union r (Semantic r) a -> Semantic r a liftSemantic u = Semantic $ \k -> k u {-# INLINE liftSemantic #-} hoistSemantic :: (∀ x. Union r (Semantic r) x -> Union r' (Semantic r') x) -> Semantic r a -> Semantic r' a hoistSemantic nat (Semantic m) = Semantic $ \k -> m $ \u -> k $ nat u {-# INLINE hoistSemantic #-} ------------------------------------------------------------------------------ -- | Introduce an effect into 'Semantic'. Analogous to -- 'Control.Monad.Class.Trans.lift' in the mtl ecosystem raise :: ∀ e r a. Semantic r a -> Semantic (e ': r) a raise = hoistSemantic $ hoist raise_b . weaken {-# INLINE raise #-} raise_b :: Semantic r a -> Semantic (e ': r) a raise_b = raise {-# NOINLINE raise_b #-} ------------------------------------------------------------------------------ -- | Lift an effect into a 'Semantic'. This is used primarily via -- 'Polysemy.makeSemantic' to implement smart constructors. send :: Member e r => e (Semantic r) a -> Semantic r a send = liftSemantic . inj {-# INLINE[3] send #-} ------------------------------------------------------------------------------ -- | Lift a monadic action @m@ into 'Semantic'. sendM :: Member (Lift m) r => m a -> Semantic r a sendM = send . Lift {-# INLINE sendM #-} ------------------------------------------------------------------------------ -- | Run a 'Semantic' containing no effects as a pure value. run :: Semantic '[] a -> a run (Semantic m) = runIdentity $ m absurdU {-# INLINE run #-} ------------------------------------------------------------------------------ -- | Lower a 'Semantic' containing only a single lifted 'Monad' into that -- monad. runM :: Monad m => Semantic '[Lift m] a -> m a runM (Semantic m) = m $ \z -> case extract z of Yo e s _ f -> do a <- unLift e pure $ f $ a <$ s {-# INLINE runM #-} ------------------------------------------------------------------------------ -- | Some interpreters need to be able to lower down to the base monad (often -- 'IO') in order to function properly --- some good examples of this are -- 'Polysemy.Error.runErrorInIO' and 'Polysemy.Resource.runResource'. -- -- However, these interpreters don't compose particularly nicely; for example, -- to run 'Polysemy.Resource.runResource', you must write: -- -- @ -- runM . runErrorInIO runM -- @ -- -- Notice that 'runM' is duplicated in two places here. The situation gets -- exponentially worse the more intepreters you have that need to run in this -- pattern. -- -- Instead, '.@' performs the composition we'd like. The above can be written as -- -- @ -- (runM .@ runErrorInIO) -- @ -- -- The parentheses here are important; without them you'll run into operator -- precedence errors. (.@) :: Monad m => (∀ x. Semantic r x -> m x) -- ^ The lowering function, likely 'runM'. -> (∀ y. (∀ x. Semantic r x -> m x) -> Semantic (e ': r) y -> Semantic r y) -> Semantic (e ': r) z -> m z f .@ g = f . g f infixl 9 .@ ------------------------------------------------------------------------------ -- | Like '.@', but for interpreters which change the resulting type --- eg. -- 'Polysemy.Error.runErrorInIO'. (.@@) :: Monad m => (∀ x. Semantic r x -> m x) -- ^ The lowering function, likely 'runM'. -> (∀ y. (∀ x. Semantic r x -> m x) -> Semantic (e ': r) y -> Semantic r (f y)) -> Semantic (e ': r) z -> m (f z) f .@@ g = f . g f infixl 9 .@@