polysemy-zoo-0.7.0.1: src/Polysemy/Cont.hs
{-# LANGUAGE AllowAmbiguousTypes, Trustworthy #-}
module Polysemy.Cont
(-- * Effect
Cont(..)
-- * Actions
, jump
, subst
, callCC
-- * Interpretations
, runContPure
, runContM
, contToFinal
-- * Experimental Interpretations
, runContViaFresh
-- * Unsafe Interpretations
, runContUnsafe
-- * Prompt types
, Ref(..)
, ExitRef(..)
, ViaFreshRef
) where
import Data.Void
import Polysemy
import Polysemy.Final
import Polysemy.Cont.Internal
import Polysemy.Error
import Polysemy.Fresh
import Control.Monad.Cont (MonadCont(), ContT(..), runContT)
import qualified Control.Monad.Cont as C (callCC)
-----------------------------------------------------------------------------
-- | Call with current continuation.
-- Executing the provided continuation will abort execution.
--
-- Using the provided continuation
-- will rollback all local effectful state back to the point where
-- 'callCC' was invoked.
--
-- Higher-order effects do not interact with the continuation in any meaningful
-- way; i.e. 'Polysemy.Reader.local' or 'Polysemy.Writer.censor' does not affect
-- it, and 'Polysemy.Error.catch' will fail to catch any of its exceptions.
-- The only exception to this is if you interpret such effects /and/ 'Cont'
-- in terms of the final monad, and the final monad can perform such interactions
-- in a meaningful manner.
callCC :: forall ref r a
. Member (Cont ref) r
=> ((forall b. a -> Sem r b) -> Sem r a)
-> Sem r a
callCC cc = subst @ref (\ref -> cc (jump ref)) pure
{-# INLINE callCC #-}
-----------------------------------------------------------------------------
-- | Runs a 'Cont' effect by providing 'pure' as the final continuation.
--
-- This is a safe variant of 'runContUnsafe', as this may only be used
-- as the final interpreter before 'run'.
runContPure :: Sem '[Cont (Ref (Sem '[]) a)] a -> Sem '[] a
runContPure = runContUnsafe
{-# INLINE runContPure #-}
-----------------------------------------------------------------------------
-- | Runs a 'Cont' effect by providing 'pure' as the final continuation.
--
-- This is a safe variant of 'runContUnsafe', as this may only be used
-- as the final interpreter before 'runM'.
runContM :: Sem '[Cont (Ref (Sem '[Embed m]) a), Embed m] a -> Sem '[Embed m] a
runContM = runContUnsafe
{-# INLINE runContM #-}
-----------------------------------------------------------------------------
-- | Runs a 'Cont' effect in terms of a final 'MonadCont'
--
-- /Beware/: Effects that aren't interpreted in terms of the final monad
-- will have local state semantics in regards to 'Cont' effects
-- interpreted this way. See 'Final'.
contToFinal :: (Member (Final m) r, MonadCont m)
=> Sem (Cont (ExitRef m) ': r) a
-> Sem r a
contToFinal = interpretFinal $ \case
Jump ref a -> pure $ enterExit ref a
Subst main cb -> do
main' <- bindS main
cb' <- bindS cb
s <- getInitialStateS
pure $ C.callCC $ \exit ->
main' (ExitRef (\a -> cb' (a <$ s) >>= vacuous . exit) <$ s)
{-# INLINE contToFinal #-}
-----------------------------------------------------------------------------
-- | A highly experimental 'Cont' interpreter that functions
-- through a combination of 'Error' and 'Fresh'. This may be used safely
-- anywhere in the effect stack.
--
-- 'runContViaFresh' is still under development.
-- You're encouraged to experiment with it, but don't rely on it.
-- For best results, use 'runContViaFresh' as the first interpreter you run,
-- such that all other effects are global in respect to it.
--
-- This interpreter may return 'Nothing' if the control flow becomes
-- split into separate, inconsistent parts,
-- such that backtracking fails when trying to invoke continuations.
-- For example, if you reify a continuation inside an
-- 'async':ed thread, and then have that thread return the reified
-- continuation back to the main thread through an 'await', then
-- 'runContViaFresh' will return 'Nothing' upon executing the continuation
-- in the main thread.
runContViaFresh :: forall uniq r a
. (Member (Fresh uniq) r, Eq uniq)
=> Sem (Cont (ViaFreshRef uniq) ': r) a
-> Sem r (Maybe a)
runContViaFresh =
let
hush (Right a) = Just a
hush _ = Nothing
in
fmap hush
. runError
. (`runContT` pure)
. runContViaFreshInC
{-# INLINE runContViaFresh #-}
-----------------------------------------------------------------------------
-- | Runs a 'Cont' effect by providing 'pure' as the final continuation.
--
-- __Beware__: This interpreter will invalidate all higher-order effects of any
-- interpreter run after it; i.e. 'Polysemy.Reader.local' and
-- 'Polysemy.Writer.censor' will be no-ops, 'Polysemy.Error.catch' will fail
-- to catch exceptions, and 'Polysemy.Writer.listen' will always return 'mempty'.
--
-- __You should therefore use 'runContUnsafe' only /after/ running all__
-- __interpreters for your higher-order effects.__
--
-- Note that 'Final' is a higher-order effect, and thus 'runContUnsafe' can't
-- safely be used together with 'runFinal'.
runContUnsafe :: Sem (Cont (Ref (Sem r) a) ': r) a -> Sem r a
runContUnsafe = runContWithCUnsafe pure
{-# INLINE runContUnsafe #-}