{-# LANGUAGE TemplateHaskell #-} module Polysemy.Final ( -- * Effect Final(..) -- * Actions , withWeaving , withStrategic , embedFinal -- * Combinators for Interpreting to the Final Monad , interpretFinal -- * Strategy -- | Strategy is a domain-specific language very similar to @Tactics@ -- (see 'Tactical'), and is used to describe how higher-order effects -- are threaded down to the final monad. -- -- Much like @Tactics@, computations can be run and threaded -- through the use of 'runS' and 'bindS', and first-order constructors -- may use 'pureS'. In addition, 'liftS' may be used to -- lift actions of the final monad. -- -- Unlike @Tactics@, the final return value within a `Strategic` -- must be a monadic value of the target monad -- with the functorial state wrapped inside of it. , Strategic , WithStrategy , pureS , liftS , runS , bindS , getInspectorS , getInitialStateS -- * Interpretations , runFinal , runFinalLift , runFinalLiftIO ) where import Data.Functor.Identity import Polysemy import Data.Functor.Compose import Polysemy.Internal import Polysemy.Internal.Tactics import Polysemy.Internal.Union import Control.Monad import Control.Monad.IO.Class ----------------------------------------------------------------------------- -- | An effect for embedding higher-order effects in the final target monad -- of the effect stack. -- -- This is very useful for writing interpreters that interpret higher-order -- effects in terms of the final monad - however, these interpreters -- are subject to very different semantics than regular ones. -- -- For more information, see 'interpretFinal'. data Final m z a where WithWeaving :: (forall f. Functor f => f () -> (forall x. f (z x) -> m (f x)) -> (forall x. f x -> Maybe x) -> m (f a) ) -> Final m z a makeSem_ ''Final ----------------------------------------------------------------------------- -- | Allows for embedding higher-order actions of the final monad -- by providing the means of explicitly threading effects through @'Sem' r@ -- to the final monad. -- -- Consider using 'withStrategic' instead, -- as it provides a more user-friendly interface to the same power. -- -- You are discouraged from using 'withWeaving' directly in application code, -- as it ties your application code directly to the underlying monad. withWeaving :: forall m a r . Member (Final m) r => (forall f. Functor f => f () -> (forall x. f (Sem r x) -> m (f x)) -> (forall x. f x -> Maybe x) -> m (f a) ) -> Sem r a ----------------------------------------------------------------------------- -- | 'withWeaving' admits an implementation of 'sendM'. -- -- Just like 'sendM', you are discouraged from using this in application code. embedFinal :: Functor m => Member (Final m) r => m a -> Sem r a embedFinal m = withWeaving $ \s _ _ -> (<$ s) <$> m ----------------------------------------------------------------------------- -- | Allows for embedding higher-order actions of the final monad -- by providing the means of explicitly threading effects through 'Sem r' -- to the final monad. This is done through the use of the 'Strategic' -- environment. -- -- You are discouraged from using 'withStrategic' in application code, -- as it ties your application code directly to the underlying monad. withStrategic :: Member (Final m) r => Strategic m (Sem r) a -> Sem r a withStrategic strat = withWeaving $ \s wv ins -> runStrategy s wv ins strat ------------------------------------------------------------------------------ -- | Like 'interpretH', but may be used to -- interpret higher-order effects in terms of the final monad. -- -- /Beware/: Any interpreters built using this (or 'Final' in general) -- will /not/ respect local/global state semantics based on the order of -- interpreters run. You should signal interpreters that make use of -- 'Final' by adding a "-Final" suffix to the names of these. -- -- State semantics of effects that are /not/ -- interpreted in terms of the final monad will always -- appear local to effects that are interpreted in terms of the final monad. -- -- State semantics between effects that are interpreted in terms of the final monad -- depend on the final monad. I.e. if the final monad is a monad transformer stack, -- then state semantics will depend on the order monad transformers are stacked. interpretFinal :: forall e m r a . (Member (Final m) r, Functor m) => (forall x n. e n x -> Strategic m n x) -> Sem (e ': r) a -> Sem r a interpretFinal n = let go :: Sem (e ': r) x -> Sem r x go (Sem sem) = sem $ \u -> case decomp u of Right (Weaving e s wv ex ins) -> fmap ex $ withWeaving $ \s' wv' ins' -> fmap getCompose $ runStrategy (Compose (s <$ s')) (fmap Compose . wv' . fmap (go . wv) . getCompose) (ins' . getCompose >=> ins) (n e) Left g -> liftSem (hoist go g) {-# INLINE go #-} in go {-# INLINE interpretFinal #-} ------------------------------------------------------------------------------ -- | 'Strategic' is an environment in which you're capable of explicitly -- threading higher-order effect states to the final monad. -- This is based upon @Tactics@, (see 'Tactical'), and usage -- is extremely similar. type Strategic m n a = forall f. Functor f => Sem (WithStrategy m f n) (m (f a)) type WithStrategy m f n = WithTactics (Embed m) f n '[] ------------------------------------------------------------------------------ -- | Get a natural transformation capable of potentially inspecting values -- inside of @f@. Binding the result of 'getInspectorS' produces a function that -- can sometimes peek inside values returned by 'bindS'. -- -- This is often useful for running callback functions that are not managed by -- polysemy code. -- -- See also 'getInspectorT' getInspectorS :: Sem (WithStrategy m f n) (Inspector f) getInspectorS = getInspectorT {-# INLINE getInspectorS #-} ------------------------------------------------------------------------------ -- | Get the stateful environment of the world at the moment the -- target monad is to be run. -- Prefer 'pureS', 'runS' or 'bindS' instead of using this function -- directly. getInitialStateS :: Sem (WithStrategy m f n) (f ()) getInitialStateS = getInitialStateT {-# INLINE getInitialStateS #-} ------------------------------------------------------------------------------ -- | Embed a value into 'Strategic'. pureS :: Applicative m => a -> Strategic m n a pureS = fmap pure . pureT {-# INLINE pureS #-} ------------------------------------------------------------------------------ -- | Lifts an action of the final monad into 'Strategic'. -- -- Note: you don't need to use this function if you already have a monadic -- action with the functorial state woven into it, by the use of -- 'runS' or 'bindS'. -- In these cases, you need only use 'pure' to embed the action into the -- 'Strategic' environment. liftS :: Functor m => m a -> Strategic m n a liftS m = do s <- getInitialStateS pure $ fmap (<$ s) m {-# INLINE liftS #-} ------------------------------------------------------------------------------ -- | Lifts a monadic action into the stateful environment, in terms -- of the final monad. -- The stateful environment will be the same as the one that the target monad -- is initially run in. -- Use 'bindS' if you'd prefer to explicitly manage your stateful environment. runS :: Monad m => n a -> Sem (WithStrategy m f n) (m (f a)) runS = fmap runM . runT {-# INLINE runS #-} ------------------------------------------------------------------------------ -- | Embed a kleisli action into the stateful environment, in terms of the final -- monad. You can use 'bindS' to get an effect parameter of the form @a -> n b@ -- into something that can be used after calling 'runS' on an effect parameter -- @n a@. bindS :: Monad m => (a -> n b) -> Sem (WithStrategy m f n) (f a -> m (f b)) bindS = fmap (runM .) . bindT {-# INLINE bindS #-} ------------------------------------------------------------------------------ -- | Internal function to process Strategies in terms of 'withWeaving'. runStrategy :: Functor f => f () -> (forall x. f (n x) -> m (f x)) -> (forall x. f x -> Maybe x) -> Sem (WithStrategy m f n) a -> a runStrategy s wv ins (Sem m) = runIdentity $ m $ \u -> case extract u of Weaving e s' _ ex' _ -> Identity $ ex' $ (<$ s') $ case e of GetInitialState -> s HoistInterpretation na -> embed . wv . fmap na GetInspector -> Inspector ins {-# INLINE runStrategy #-} ------------------------------------------------------------------------------ -- | Lower a 'Sem' containing only a lifted, final monad into that monad. -- The appearance of 'Lift' as the final effect -- is to allow the use of operations that rely on a @'LastMember' ('Lift' m)@ -- constraint. runFinal :: Monad m => Sem '[Final m, Embed m] a -> m a runFinal = usingSem $ \u -> case decomp u of Right (Weaving (WithWeaving wav) s wv ex ins) -> ex <$> wav s (runFinal . wv) ins Left g -> case extract g of Weaving (Embed m) s _ ex _ -> ex . (<$ s) <$> m {-# INLINE runFinal #-} ------------------------------------------------------------------------------ -- | Lower a 'Sem' containing two lifted monad into the final monad, -- by interpreting one of the monads in terms of the other one. -- -- This allows for the use of operations that rely on a @'LastMember' ('Lift' m)@ -- constraint, as long as @m@ can be transformed to the final monad; -- but be warned, this breaks the implicit contract of @'LastMember' ('Lift' m)@ -- that @m@ /is/ the final monad, so depending on the final monad and operations -- used, 'runFinalLift' may become /unsafe/. -- -- For example, 'runFinalLift' is unsafe with 'Polysemy.Async.asyncToIO' if -- the final monad is non-deterministic, or a continuation -- monad. runFinalLift :: Monad m => (forall x. n x -> m x) -> Sem [Final m, Embed m, Embed n] a -> m a runFinalLift nat = usingSem $ \u -> case decomp u of Right (Weaving (WithWeaving wav) s wv ex ins) -> ex <$> wav s (runFinalLift nat . wv) ins Left g -> case decomp g of Right (Weaving (Embed m) s _ ex _) -> ex . (<$ s) <$> m Left g' -> case extract g' of Weaving (Embed n) s _ ex _ -> ex . (<$ s) <$> nat n {-# INLINE runFinalLift #-} ------------------------------------------------------------------------------ -- | 'runFinalLift', specialized to transform 'IO' to a 'MonadIO'. runFinalLiftIO :: MonadIO m => Sem [Final m, Embed m, Embed IO] a -> m a runFinalLiftIO = runFinalLift liftIO {-# INLINE runFinalLiftIO #-}