diff --git a/polysemy.cabal b/polysemy.cabal index 19fd479c..9a0ed51b 100644 --- a/polysemy.cabal +++ b/polysemy.cabal @@ -4,7 +4,7 @@ cabal-version: 2.0 -- -- see: https://github.com/sol/hpack -- --- hash: 754ab355722062c11ee014b832c3c95ddeea81fec4242a5938436c0ca64383c8 +-- hash: d7bfeced9fb04f06fed1d7d70c5ba8bbb99be9e4bf37c0b6e514fca6891a1405 name: polysemy version: 1.4.0.0 @@ -64,6 +64,7 @@ library Polysemy.Internal.CustomErrors.Redefined Polysemy.Internal.Fixpoint Polysemy.Internal.Forklift + Polysemy.Internal.Interpretation Polysemy.Internal.Kind Polysemy.Internal.NonDet Polysemy.Internal.Strategy @@ -73,6 +74,7 @@ library Polysemy.Internal.Union Polysemy.Internal.WeaveClass Polysemy.Internal.Writer + Polysemy.Interpretation Polysemy.IO Polysemy.Law Polysemy.Membership diff --git a/src/Polysemy.hs b/src/Polysemy.hs index 57ea333f..462e9572 100644 --- a/src/Polysemy.hs +++ b/src/Polysemy.hs @@ -107,6 +107,7 @@ module Polysemy , transform -- * Combinators for Interpreting Higher-Order Effects + , EffHandlerH , interpretNew , interceptNew , reinterpretNew @@ -135,9 +136,15 @@ module Polysemy -- | When interpreting higher-order effects using 'interpretNew' -- and friends, you can't execute higher-order \"thunks\" given by -- the interpreted effect directly. Instead, these must be executed - -- using 'runH'. + -- using 'runH' or 'runH''. + -- + -- These functions are enough for most purposes when using + -- 'interpretNew'. However, "Polysemy.Interpretation" offers + -- additional, more complicated features which are occassionally + -- needed. , RunH , runH + , runH' -- * Tactics -- | Higher-order effects need to explicitly thread /other effects'/ state @@ -164,6 +171,7 @@ module Polysemy import Polysemy.Final import Polysemy.Internal import Polysemy.Internal.Combinators +import Polysemy.Internal.Interpretation import Polysemy.Internal.Forklift import Polysemy.Internal.Kind import Polysemy.Internal.Tactics diff --git a/src/Polysemy/Async.hs b/src/Polysemy/Async.hs index e1aa2b66..c5e40e7c 100644 --- a/src/Polysemy/Async.hs +++ b/src/Polysemy/Async.hs @@ -21,6 +21,7 @@ module Polysemy.Async import qualified Control.Concurrent.Async as A import Polysemy import Polysemy.Final +import Polysemy.Interpretation @@ -72,16 +73,16 @@ asyncToIO => Sem (Async ': r) a -> Sem r a asyncToIO m = withLowerToIO $ \lower _ -> lower $ - interpretH + interpretNew ( \case - Async a -> do - ma <- runT a - ins <- getInspectorT - fa <- embed $ A.async $ lower $ asyncToIO ma - pureT $ inspect ins <$> fa - - Await a -> pureT =<< embed (A.wait a) - Cancel a -> pureT =<< embed (A.cancel a) + Async ma -> do + Processor pr <- getProcessorH' + fa <- embed $ A.async $ lower $ asyncToIO (pr ma) + let ins = foldr (const . Just) Nothing + return (fmap ins fa) + + Await a -> embed (A.wait a) + Cancel a -> embed (A.cancel a) ) m {-# INLINE asyncToIO #-} @@ -126,16 +127,16 @@ lowerAsync -- some combination of 'runM' and other interpreters composed via '.@'. -> Sem (Async ': r) a -> Sem r a -lowerAsync lower m = interpretH +lowerAsync lower m = interpretNew ( \case - Async a -> do - ma <- runT a - ins <- getInspectorT - fa <- embed $ A.async $ lower $ lowerAsync lower ma - pureT $ inspect ins <$> fa - - Await a -> pureT =<< embed (A.wait a) - Cancel a -> pureT =<< embed (A.cancel a) + Async ma -> do + Processor pr <- getProcessorH + let ins = foldr (const . Just) Nothing + fa <- embed $ A.async $ lower $ pr ma + return $ ins <$> fa + + Await a -> embed (A.wait a) + Cancel a -> embed (A.cancel a) ) m {-# INLINE lowerAsync #-} {-# DEPRECATED lowerAsync "Use 'asyncToIOFinal' instead" #-} diff --git a/src/Polysemy/Error.hs b/src/Polysemy/Error.hs index 2cbc6ae1..eb742cfe 100644 --- a/src/Polysemy/Error.hs +++ b/src/Polysemy/Error.hs @@ -33,6 +33,7 @@ import Data.Bifunctor (first) import Data.Typeable import Polysemy import Polysemy.Final +import Polysemy.Interpretation import Polysemy.Internal import Polysemy.Internal.Union @@ -217,21 +218,12 @@ mapError => (e1 -> e2) -> Sem (Error e1 ': r) a -> Sem r a -mapError f = interpretH $ \case +mapError f = interpretNew $ \case Throw e -> throw $ f e - Catch action handler -> do - a <- runT action - h <- bindT handler - - mx <- raise $ runError a - case mx of + Catch action handler -> + runError (runH' action) >>= \case Right x -> pure x - Left e -> do - istate <- getInitialStateT - mx' <- raise $ runError $ h $ e <$ istate - case mx' of - Right x -> pure x - Left e' -> throw $ f e' + Left e -> runH (handler e) {-# INLINE mapError #-} @@ -318,13 +310,12 @@ runErrorAsExc => (∀ x. Sem r x -> IO x) -> Sem (Error e ': r) a -> Sem r a -runErrorAsExc lower = interpretH $ \case +runErrorAsExc lower = interpretNew $ \case Throw e -> embed $ X.throwIO $ WrappedExc e Catch main handle -> do - is <- getInitialStateT - m <- runT main - h <- bindT handle - let runIt = lower . runErrorAsExc lower - embed $ X.catch (runIt m) $ \(se :: WrappedExc e) -> - runIt $ h $ unwrapExc se <$ is + Processor pr <- getProcessorH + let runIt = lower . pr + ta <- embed $ X.catch (runIt main) $ \(se :: WrappedExc e) -> + runIt $ handle $ unwrapExc se + restoreH ta {-# INLINE runErrorAsExc #-} diff --git a/src/Polysemy/Internal/Combinators.hs b/src/Polysemy/Internal/Combinators.hs index 128aa0b5..06fffce2 100644 --- a/src/Polysemy/Internal/Combinators.hs +++ b/src/Polysemy/Internal/Combinators.hs @@ -12,16 +12,6 @@ module Polysemy.Internal.Combinators , rewrite , transform - -- * Higher order - , RunH(..) - , runH - - , interpretNew - , interceptNew - , reinterpretNew - , reinterpret2New - , reinterpret3New - -- * Higher order with 'Tactical' , interpretH , interceptH @@ -32,7 +22,6 @@ module Polysemy.Internal.Combinators -- * Conditional , interceptUsing , interceptUsingH - , interceptUsingNew -- * Statefulness , stateful @@ -419,149 +408,3 @@ transform f (Sem m) = Sem $ \k -> m $ \u -> Left g -> g Right (Weaving e mkT lwr ex) -> injWeaving (Weaving (f e) mkT lwr ex) - - --- | An effect for running monadic actions within a higher-order effect --- currently being interpreted. -newtype RunH z (m :: * -> *) a where - RunH :: z a -> RunH z m a - --- | Run a monadic action given by a higher-order effect that is currently --- being interpreted. --- --- @since TODO -runH :: Member (RunH z) r => z a -> Sem r a -runH = send . RunH - ------------------------------------------------------------------------------- --- | Like 'interpret', but for higher-order effects (i.e. those which make use --- of the @m@ parameter.) --- --- This is significantly easier to use than 'interpretH' and its corresponding --- 'Tactical' environment. --- Because of this, 'interpretNew' and friends are /heavily recommended/ over --- 'interpretH' and friends /unless/ you need the extra power that the 'Tactical' --- environment provides -- the ability to inspect and manipulate the underlying --- effectful state. --- --- Higher-order thunks within the effect to be interpreted can be run using --- 'runH'. For example: --- --- @ --- data Bind m a where --- Bind :: m a -> (a -> m b) -> Bind m b --- --- runBind :: Sem (Bind ': r) a -> Sem r a --- runBind = 'interpretNew' \\case --- Bind ma f -> do --- a <- 'runH' ma --- b <- 'runH' (f a) --- return b --- @ --- --- @since TODO -interpretNew :: forall e r a - . (forall z x. e z x -> Sem (RunH z ': r) x) - -> Sem (e ': r) a - -> Sem r a -interpretNew h (Sem sem) = Sem $ \(k :: forall x. Union r (Sem r) x -> m x) -> - sem $ \u -> case decomp (hoist (interpretNew h) u) of - Left g -> k g - Right (Weaving e - (mkT :: forall n x - . Monad n - => (forall y. Sem r y -> n y) - -> z x -> t n x - ) - lwr - ex - ) -> - let - go1 :: forall x. Sem (RunH z ': r) x -> t m x - go1 = usingSem $ \u' -> case decomp u' of - Right (Weaving (RunH z) _ lwr' ex') -> - (ex' . (<$ mkInitState lwr')) <$> mkT (usingSem k) z - Left g -> liftHandlerWithNat go2 k g - - go2 :: forall x. Sem (RunH z ': r) x -> t (Sem r) x - go2 = usingSem $ \u' -> case decomp (hoist go2 u') of - Right (Weaving (RunH z) _ lwr' ex') -> - (ex' . (<$ mkInitState lwr')) <$> mkT id z - Left g -> liftHandler liftSem g - in - fmap ex $ lwr $ go1 (h e) - --- TODO (KingoftheHomeless): If it matters, optimize the definitions --- below - ------------------------------------------------------------------------------- --- | Like 'reinterpret', but for higher-order effects. --- --- This is /heavily recommended/ over 'reinterpretH' unless you need --- the extra power that the 'Tactical' environment provides. --- --- @since TODO -reinterpretNew :: forall e1 e2 r a - . (forall z x. e1 z x -> Sem (RunH z ': e2 ': r) x) - -> Sem (e1 ': r) a - -> Sem (e2 ': r) a -reinterpretNew h = interpretNew h . raiseUnder -{-# INLINE reinterpretNew #-} - ------------------------------------------------------------------------------- --- | Like 'reinterpret2', but for higher-order effects. --- --- This is /heavily recommended/ over 'reinterpret2H' unless you need --- the extra power that the 'Tactical' environment provides. --- --- @since TODO -reinterpret2New :: forall e1 e2 e3 r a - . (forall z x. e1 z x -> Sem (RunH z ': e2 ': e3 ': r) x) - -> Sem (e1 ': r) a - -> Sem (e2 ': e3 ': r) a -reinterpret2New h = interpretNew h . raiseUnder2 -{-# INLINE reinterpret2New #-} - ------------------------------------------------------------------------------- --- | Like 'reinterpret3', but for higher-order effects. --- --- This is /heavily recommended/ over 'reinterpret3H' unless you need --- the extra power that the 'Tactical' environment provides. --- --- @since TODO -reinterpret3New :: forall e1 e2 e3 e4 r a - . (forall z x. e1 z x -> Sem (RunH z ': e2 ': e3 ': e4 ': r) x) - -> Sem (e1 ': r) a - -> Sem (e2 ': e3 ': e4 ': r) a -reinterpret3New h = interpretNew h . raiseUnder3 -{-# INLINE reinterpret3New #-} - ------------------------------------------------------------------------------- --- | Like 'intercept', but for higher-order effects. --- --- This is /heavily recommended/ over 'interceptH' unless you need --- the extra power that the 'Tactical' environment provides. --- --- @since TODO -interceptNew :: forall e r a - . Member e r - => (forall z x. e z x -> Sem (RunH z ': r) x) - -> Sem r a - -> Sem r a -interceptNew h = interpretNew h . expose -{-# INLINE interceptNew #-} - ------------------------------------------------------------------------------- --- | Like 'interceptUsing', but for higher-order effects. --- --- This is /heavily recommended/ over 'interceptUsingH' unless you need --- the extra power that the 'Tactical' environment provides. --- --- @since TODO -interceptUsingNew :: forall e r a - . ElemOf e r - -> (forall z x. e z x -> Sem (RunH z ': r) x) - -> Sem r a - -> Sem r a -interceptUsingNew pr h = interpretNew h . exposeUsing pr -{-# INLINE interceptUsingNew #-} diff --git a/src/Polysemy/Internal/Interpretation.hs b/src/Polysemy/Internal/Interpretation.hs new file mode 100644 index 00000000..729aa096 --- /dev/null +++ b/src/Polysemy/Internal/Interpretation.hs @@ -0,0 +1,313 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# OPTIONS_HADDOCK not-home #-} +module Polysemy.Internal.Interpretation where + +import Polysemy.Internal +import Polysemy.Internal.WeaveClass +import Polysemy.Internal.Union +import Polysemy.Internal.Kind + + +newtype Processor z t r = Processor { getProcessor :: forall x. z x -> Sem r (t x) } + +-- | An effect for running monadic actions within a higher-order effect +-- currently being interpreted. +data RunH z t e r :: Effect where + RunH :: forall z t e r m a. z a -> RunH z t e r m a + GetProcessorH :: forall z t e r m. RunH z t e r m (Processor z t r) + GetProcessorH' :: forall z t e r m. RunH z t e r m (Processor z t (e ': r)) + ExposeH :: forall z t e r m a. m a -> RunH z t e r m (t a) + RestoreH :: forall z t e r m a. t a -> RunH z t e r m a + +-- | Run a monadic action given by a higher-order effect that is currently +-- being interpreted, and recursively apply the current interpreter on it. +-- +-- This is the standard tool for interpreting higher-order effects. +-- +-- @since TODO +runH :: forall z t e r r' a. z a -> Sem (RunH z t e r ': r') a +runH = send . RunH @z @t @e @r +{-# INLINE runH #-} + +-- | Run a monadic action given by a higher-order effect that is currently +-- being interpreted. +-- +-- Unlike 'runH', this doesn't recursively apply the current interpreter +-- to the monadic action -- allowing you to run a different interpreter +-- on it instead. +-- +-- @since TODO +runH' :: forall z t e r a. z a -> Sem (e ': RunH z t e r ': r) a +runH' z = runExposeH' z >>= raise . restoreH +{-# INLINE runH' #-} + +-- | Run a monadic action given by a higher-order effect that is currently +-- being interpreted, recursively apply the current interpreter on it, +-- and reify the effectful state of all local effects +-- as part of the result. +-- +-- By reifying the effectful state, you may do one or more of the following: +-- +-- * Guarantee that the handler won't be interrupted by a local effect failing, +-- since that failure will instead be reified into the state. +-- * Check if the action run has failed because of a local effect by using 'Data.Foldable.null'. +-- * Discard any impact the monadic action has on local effects by never restoring the +-- efectful state. +-- +-- Once an effectful state has been reified, you may restore it using 'restoreH'. +-- +-- @since TODO +runExposeH :: forall z t e r a. z a -> Sem (RunH z t e r ': r) (t a) +runExposeH z = do + Processor pr <- getProcessorH + raise (pr z) +{-# INLINE runExposeH #-} + +-- | Run a monadic action given by a higher-order effect that is currently +-- being interpreted, and reify the effectful state of all local effects +-- as part of the result. +-- +-- See 'runExposeH' for more information. +-- +-- Unlike 'runH', this doesn't recursively apply the current interpreter +-- to the monadic action -- allowing you to run a different interpreter +-- on it instead. +-- +-- @since TODO +runExposeH' :: forall z t e r a. z a -> Sem (e ': RunH z t e r ': r) (t a) +runExposeH' z = do + Processor pr <- raise getProcessorH' + raiseUnder (pr z) +{-# INLINE runExposeH' #-} + + + +-- | Restore a reified effectful state, bringing its changes into scope, and returning +-- the result of the computation. +-- +-- /Note/: this overrides the local effectful state of any previously restored effectful state. +-- +-- For example, consider: +-- +-- @ +-- 'ta' <- runExposeH ma +-- 'tb' <- runExposeH mb +-- 'restoreH' ta +-- 'restoreH' tb +-- @ +-- Unless @'restoreH' ta@ causes the handler to fail (because @ma@ failed due to a local effect), +-- the changes it brings into scope will be overridden by @'restoreH' tb@. +-- +-- If you want to integrate the results of both actions, you need to restore the state +-- in between uses of 'runExposeH', so that @'runExposeH' mb@ works with the changes of @ta@ +-- in scope. +-- @ +-- 'ta' <- runExposeH ma +-- 'restoreH' ta +-- 'tb' <- runExposeH mb +-- 'restoreH' tb +-- @ +-- +-- @since TODO +restoreH :: forall z t e r r' a. t a -> Sem (RunH z t e r ': r') a +restoreH = send . RestoreH @z @_ @e @r +{-# INLINE restoreH #-} + + +-- | Reify the effectful state of the local effects of the argument. +-- +-- @'runExposeH' m = 'exposeH' ('runH' m)@ +-- +-- /Note/: `polysemy-plugin` is heavily recommended when using this function +-- to avoid type ambiguous types. If `polysemy-plugin` isn't available, consider +-- using 'runExposeH' and `runExposeH'` instead. +-- +-- @since TODO +exposeH :: forall z t e r r' a. Member (RunH z t e r) r' => Sem r' a -> Sem r' (t a) +exposeH = send . ExposeH @z @_ @e @r +{-# INLINE exposeH #-} + +-- | Retrieve a 'Processor': a function which can be used +-- to process a monadic action given by a higher-order effect that is currently +-- being interpreted without immediately running it, turning it into a @'Sem' r@ action +-- that returns a reified effectful state. +-- +-- The processor automatically recursively applies the current interpreter on +-- monadic actions processed. +getProcessorH :: forall z t e r r'. Sem (RunH z t e r ': r') (Processor z t r) +getProcessorH = send (GetProcessorH @_ @_ @e) +{-# INLINE getProcessorH #-} + +-- | Retrieve a 'Processor': a function which can be used +-- to process a monadic action given by a higher-order effect that is currently +-- being interpreted without immediately running it, turning it into a @'Sem' (e ': r)@ action +-- that returns a reified effectful state. +getProcessorH' :: forall z t e r r'. Sem (RunH z t e r ': r') (Processor z t (e ': r)) +getProcessorH' = send GetProcessorH' +{-# INLINE getProcessorH' #-} + +type EffHandlerH e r = + forall z t x + . Traversable t + => e z x -> Sem (RunH z t e r ': r) x + +------------------------------------------------------------------------------ +-- | Like 'interpret', but for higher-order effects (i.e. those which make use +-- of the @m@ parameter.) +-- +-- This is significantly easier to use than 'interpretH' and its corresponding +-- 'Tactical' environment. +-- Because of this, 'interpretNew' and friends are /heavily recommended/ over +-- 'interpretH' and friends /unless/ you need the extra power that the 'Tactical' +-- environment provides -- the ability to inspect and manipulate the underlying +-- effectful state. +-- +-- Higher-order thunks within the effect to be interpreted can be run using +-- 'runH'. For example: +-- +-- @ +-- data Bind m a where +-- Bind :: m a -> (a -> m b) -> Bind m b +-- +-- runBind :: Sem (Bind ': r) a -> Sem r a +-- runBind = 'interpretNew' \\case +-- Bind ma f -> do +-- a <- 'runH' ma +-- b <- 'runH' (f a) +-- return b +-- @ +-- +-- @since TODO +interpretNew :: forall e r a + . EffHandlerH e r + -> Sem (e ': r) a + -> Sem r a +interpretNew h (Sem sem) = Sem $ \(k :: forall x. Union r (Sem r) x -> m x) -> + sem $ \u -> case decomp u of + Left g -> k $ hoist (interpretNew h) g + Right (Weaving e + (mkT :: forall n x + . Monad n + => (forall y. Sem (e ': r) y -> n y) + -> z x -> t n x + ) + lwr + ex + ) -> + let + go1 :: forall x. Sem (RunH z (StT t) e r ': r) x -> t m x + go1 = usingSem $ \u' -> case decomp u' of + Left g -> liftHandlerWithNat go2 k g + Right (Weaving eff mkT' lwr' ex') -> do + let run_it = fmap (ex' . (<$ mkInitState lwr')) + case eff of + RunH z -> run_it $ + mkT (usingSem k . interpretNew h) z + GetProcessorH -> run_it $ + liftWith $ \lower -> return $ Processor (lower . mkT (interpretNew h)) + GetProcessorH' -> run_it $ + liftWith $ \lower -> return $ Processor (lower . mkT id) + RestoreH t -> run_it $ + restoreT (return t) + ExposeH m -> fmap ex' $ lwr' $ controlT $ \lower' -> do + let m' = lower' (mkT' go1 m) + liftWith $ \lower -> do + t <- lower m' + lower' $ traverse (restoreT . return) t + + go2 :: forall x. Sem (RunH z (StT t) e r ': r) x -> t (Sem r) x + go2 = usingSem $ \u' -> case decomp u' of + Left g -> liftHandlerWithNat go2 liftSem g + Right (Weaving eff mkT' lwr' ex') -> do + let run_it = fmap (ex' . (<$ mkInitState lwr')) + case eff of + RunH z -> run_it $ + mkT (interpretNew h) z + GetProcessorH -> run_it $ + liftWith $ \lower -> return $ Processor (lower . mkT (interpretNew h)) + GetProcessorH' -> run_it $ + liftWith $ \lower -> return $ Processor (lower . mkT id) + RestoreH t -> run_it $ + restoreT (return t) + ExposeH m -> fmap ex' $ lwr' $ controlT $ \lower' -> do + let m' = lower' (mkT' go2 m) + liftWith $ \lower -> do + t <- lower m' + lower' $ traverse (restoreT . return) t + in + fmap ex $ lwr $ go1 (h e) + +-- TODO (KingoftheHomeless): If it matters, optimize the definitions +-- below + +------------------------------------------------------------------------------ +-- | Like 'reinterpret', but for higher-order effects. +-- +-- This is /heavily recommended/ over 'reinterpretH' unless you need +-- the extra power that the 'Tactical' environment provides. +-- +-- @since TODO +reinterpretNew :: forall e1 e2 r a + . EffHandlerH e1 (e2 ': r) + -> Sem (e1 ': r) a + -> Sem (e2 ': r) a +reinterpretNew h = interpretNew h . raiseUnder +{-# INLINE reinterpretNew #-} + +------------------------------------------------------------------------------ +-- | Like 'reinterpret2', but for higher-order effects. +-- +-- This is /heavily recommended/ over 'reinterpret2H' unless you need +-- the extra power that the 'Tactical' environment provides. +-- +-- @since TODO +reinterpret2New :: forall e1 e2 e3 r a + . EffHandlerH e1 (e2 ': e3 ': r) + -> Sem (e1 ': r) a + -> Sem (e2 ': e3 ': r) a +reinterpret2New h = interpretNew h . raiseUnder2 +{-# INLINE reinterpret2New #-} + +------------------------------------------------------------------------------ +-- | Like 'reinterpret3', but for higher-order effects. +-- +-- This is /heavily recommended/ over 'reinterpret3H' unless you need +-- the extra power that the 'Tactical' environment provides. +-- +-- @since TODO +reinterpret3New :: forall e1 e2 e3 e4 r a + . EffHandlerH e1 (e2 ': e3 ': e4 ': r) + -> Sem (e1 ': r) a + -> Sem (e2 ': e3 ': e4 ': r) a +reinterpret3New h = interpretNew h . raiseUnder3 +{-# INLINE reinterpret3New #-} + +------------------------------------------------------------------------------ +-- | Like 'intercept', but for higher-order effects. +-- +-- This is /heavily recommended/ over 'interceptH' unless you need +-- the extra power that the 'Tactical' environment provides. +-- +-- @since TODO +interceptNew :: forall e r a + . Member e r + => EffHandlerH e r + -> Sem r a + -> Sem r a +interceptNew h = interpretNew h . expose +{-# INLINE interceptNew #-} + +------------------------------------------------------------------------------ +-- | Like 'interceptUsing', but for higher-order effects. +-- +-- This is /heavily recommended/ over 'interceptUsingH' unless you need +-- the extra power that the 'Tactical' environment provides. +-- +-- @since TODO +interceptUsingNew :: forall e r a + . ElemOf e r + -> EffHandlerH e r + -> Sem r a + -> Sem r a +interceptUsingNew pr h = interpretNew h . exposeUsing pr +{-# INLINE interceptUsingNew #-} diff --git a/src/Polysemy/Internal/Union.hs b/src/Polysemy/Internal/Union.hs index e72691b9..78c5906b 100644 --- a/src/Polysemy/Internal/Union.hs +++ b/src/Polysemy/Internal/Union.hs @@ -82,7 +82,7 @@ instance Functor (Union r mWoven) where data Weaving e mAfter resultType where Weaving - :: forall t e rInitial a resultType mAfter. (MonadTransControl t) + :: forall t e rInitial a resultType mAfter. (MonadTransWeave t) => { weaveEffect :: e (Sem rInitial) a -- ^ The original effect GADT originally lifted via @@ -100,7 +100,7 @@ instance Functor (Weaving e m) where -weave :: (MonadTransControl t, Monad n) +weave :: (MonadTransWeave t, Monad n) => (forall x. m x -> t n x) -> (forall z x. Monad z => t z x -> z (StT t x)) -> Union r m a @@ -112,13 +112,13 @@ weave mkT' lwr' (Union pr (Weaving e mkT lwr ex)) = (fmap ex . getCompose) {-# INLINE weave #-} -liftHandler :: (MonadTransControl t, Monad m, Monad n) +liftHandler :: (MonadTransWeave t, Monad m, Monad n) => (forall x. Union r m x -> n x) -> Union r (t m) a -> t n a liftHandler = liftHandlerWithNat id {-# INLINE liftHandler #-} -liftHandlerWithNat :: (MonadTransControl t, Monad m, Monad n) +liftHandlerWithNat :: (MonadTransWeave t, Monad m, Monad n) => (forall x. q x -> t m x) -> (forall x. Union r m x -> n x) -> Union r q a -> t n a diff --git a/src/Polysemy/Internal/WeaveClass.hs b/src/Polysemy/Internal/WeaveClass.hs index 5d500f89..e636540f 100644 --- a/src/Polysemy/Internal/WeaveClass.hs +++ b/src/Polysemy/Internal/WeaveClass.hs @@ -1,8 +1,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving, QuantifiedConstraints, TupleSections #-} {-# OPTIONS_HADDOCK not-home #-} module Polysemy.Internal.WeaveClass - ( MonadTransControl(..) - , controlT + ( MonadTransWeave(..) , mkInitState , mkDistrib @@ -26,14 +25,14 @@ import qualified Control.Monad.Trans.State.Lazy as LSt import qualified Control.Monad.Trans.State.Strict as SSt import qualified Control.Monad.Trans.Writer.Lazy as LWr --- | A variant of the classic @MonadTransControl@ class from @monad-control@, +-- | A variant of the classic @MonadTransWeave@ class from @monad-control@, -- but with a small number of changes to make it more suitable with Polysemy's -- internals. class ( MonadTrans t , forall z. Monad z => Monad (t z) , Traversable (StT t) ) - => MonadTransControl t where + => MonadTransWeave t where type StT t :: * -> * hoistT :: (Monad m, Monad n) @@ -42,18 +41,18 @@ class ( MonadTrans t hoistT n m = controlT $ \lower -> n (lower m) {-# INLINE hoistT #-} + controlT :: Monad m + => ((forall z x. Monad z => t z x -> z (StT t x)) -> m (StT t a)) + -> t m a + controlT main = liftWith main >>= restoreT . pure + {-# INLINE controlT #-} + liftWith :: Monad m => ((forall z x. Monad z => t z x -> z (StT t x)) -> m a) -> t m a restoreT :: Monad m => m (StT t a) -> t m a -controlT :: (MonadTransControl t, Monad m) - => ((forall z x. Monad z => t z x -> z (StT t x)) -> m (StT t a)) - -> t m a -controlT main = liftWith main >>= restoreT . pure -{-# INLINE controlT #-} - newtype ComposeT t (u :: (* -> *) -> * -> *) m a = ComposeT { getComposeT :: t (u m) a } @@ -66,14 +65,19 @@ instance ( MonadTrans t => MonadTrans (ComposeT t u) where lift m = ComposeT (lift (lift m)) -instance ( MonadTransControl t - , MonadTransControl u +instance ( MonadTransWeave t + , MonadTransWeave u ) - => MonadTransControl (ComposeT t u) where + => MonadTransWeave (ComposeT t u) where type StT (ComposeT t u) = Compose (StT u) (StT t) hoistT n (ComposeT m) = ComposeT (hoistT (hoistT n) m) + controlT main = ComposeT $ + controlT $ \lowerT -> + controlT $ \lowerU -> + getCompose <$> main (\(ComposeT m) -> Compose <$> lowerU (lowerT m)) + liftWith main = ComposeT $ liftWith $ \lowerT -> liftWith $ \lowerU -> @@ -89,7 +93,7 @@ mkInitState :: Monad (t Identity) mkInitState lwr = runIdentity $ lwr (pure ()) {-# INLINE mkInitState #-} -mkDistrib :: (MonadTransControl t, Monad m) +mkDistrib :: (MonadTransWeave t, Monad m) => (forall n x. Monad n => (forall y. m y -> n y) -> q x -> t n x) -> (forall z x. Monad z => t z x -> z (StT t x)) -> Distrib (StT t) q m @@ -100,7 +104,7 @@ mkInspector :: Foldable f => f a -> Maybe a mkInspector = foldr (const . Just) Nothing {-# INLINE mkInspector #-} -instance MonadTransControl IdentityT where +instance MonadTransWeave IdentityT where type StT IdentityT = Identity hoistT = (coerce :: (m x -> n x) -> IdentityT m x -> IdentityT n x) @@ -108,52 +112,64 @@ instance MonadTransControl IdentityT where restoreT = IdentityT . fmap runIdentity -instance MonadTransControl (LSt.StateT s) where +instance MonadTransWeave (LSt.StateT s) where type StT (LSt.StateT s) = (,) s hoistT = LSt.mapStateT + controlT main = LSt.StateT $ \s -> + swap <$> main (\m -> swap <$> LSt.runStateT m s) + liftWith main = LSt.StateT $ \s -> (, s) <$> main (\m -> swap <$> LSt.runStateT m s) restoreT m = LSt.StateT $ \_ -> swap <$> m -instance MonadTransControl (SSt.StateT s) where +instance MonadTransWeave (SSt.StateT s) where type StT (SSt.StateT s) = (,) s hoistT = SSt.mapStateT + controlT main = SSt.StateT $ \s -> + swap <$!> main (\m -> swap <$!> SSt.runStateT m s) + liftWith main = SSt.StateT $ \s -> (, s) <$> main (\m -> swap <$!> SSt.runStateT m s) restoreT m = SSt.StateT $ \_ -> swap <$!> m -instance MonadTransControl (E.ExceptT e) where +instance MonadTransWeave (E.ExceptT e) where type StT (E.ExceptT e) = Either e hoistT = E.mapExceptT + controlT main = E.ExceptT (main E.runExceptT) + liftWith main = lift $ main E.runExceptT restoreT = E.ExceptT -instance Monoid w => MonadTransControl (LWr.WriterT w) where +instance Monoid w => MonadTransWeave (LWr.WriterT w) where type StT (LWr.WriterT w) = (,) w hoistT = LWr.mapWriterT + controlT main = LWr.WriterT (swap <$> main (fmap swap . LWr.runWriterT)) + liftWith main = lift $ main (fmap swap . LWr.runWriterT) restoreT m = LWr.WriterT (swap <$> m) -instance MonadTransControl MaybeT where +instance MonadTransWeave MaybeT where type StT MaybeT = Maybe hoistT = mapMaybeT + controlT main = MaybeT (main runMaybeT) + liftWith main = lift $ main runMaybeT restoreT = MaybeT diff --git a/src/Polysemy/Internal/Writer.hs b/src/Polysemy/Internal/Writer.hs index 73482034..c80f6ea2 100644 --- a/src/Polysemy/Internal/Writer.hs +++ b/src/Polysemy/Internal/Writer.hs @@ -41,25 +41,15 @@ writerToEndoWriter :: (Monoid o, Member (Writer (Endo o)) r) => Sem (Writer o ': r) a -> Sem r a -writerToEndoWriter = interpretH $ \case - Tell o -> tell (Endo (o <>)) >>= pureT +writerToEndoWriter = interpretNew $ \case + Tell o -> tell (Endo (o <>)) Listen m -> do - m' <- writerToEndoWriter <$> runT m - raise $ do - (o, fa) <- listen m' - return $ (,) (appEndo o mempty) <$> fa - Pass m -> do - ins <- getInspectorT - m' <- writerToEndoWriter <$> runT m - raise $ pass $ do - t <- m' - let - f' = - maybe - id - (\(f, _) (Endo oo) -> let !o' = f (oo mempty) in Endo (o' <>)) - (inspect ins t) - return (f', snd <$> t) + (o, a) <- listen (runH m) + return (appEndo o mempty, a) + Pass m -> pass $ do + (f, a) <- runH m + let f' (Endo oo) = let !o' = f (oo mempty) in Endo (o' <>) + return (f', a) {-# INLINE writerToEndoWriter #-} @@ -76,37 +66,31 @@ runWriterSTMAction :: forall o r a => (o -> STM ()) -> Sem (Writer o ': r) a -> Sem r a -runWriterSTMAction write = interpretH $ \case - Tell o -> do - t <- embedFinal $ atomically (write o) - pureT t +runWriterSTMAction write = interpretNew $ \case + Tell o -> embedFinal $ atomically (write o) Listen m -> do - m' <- runT m -- Using 'withWeavingToFinal' instead of 'withStrategicToFinal' -- here allows us to avoid using two additional 'embedFinal's in -- order to create the TVars. - raise $ withWeavingToFinal $ \s wv _ -> mask $ \restore -> do + withWeavingToFinal $ \s wv _ -> mask $ \restore -> do -- See below to understand how this works tvar <- newTVarIO mempty switch <- newTVarIO False fa <- - restore (wv (runWriterSTMAction (writeListen tvar switch) m' <$ s)) + restore (wv (runWriterSTMAction (writeListen tvar switch) (runH' m) <$ s)) `onException` commitListen tvar switch o <- commitListen tvar switch - return $ (fmap . fmap) (o, ) fa + return $ fmap (o, ) fa Pass m -> do - m' <- runT m - ins <- getInspectorT - raise $ withWeavingToFinal $ \s wv ins' -> mask $ \restore -> do + withWeavingToFinal $ \s wv ins' -> mask $ \restore -> do -- See below to understand how this works tvar <- newTVarIO mempty switch <- newTVarIO False t <- - restore (wv (runWriterSTMAction (writePass tvar switch) m' <$ s)) + restore (wv (runWriterSTMAction (writePass tvar switch) (runH' m) <$ s)) `onException` commitPass tvar switch id - commitPass tvar switch - (maybe id fst $ ins' t >>= inspect ins) - return $ (fmap . fmap) snd t + commitPass tvar switch $ maybe id fst (ins' t) + return $ fmap snd t where {- KingoftheHomeless: diff --git a/src/Polysemy/Interpretation.hs b/src/Polysemy/Interpretation.hs new file mode 100644 index 00000000..22437356 --- /dev/null +++ b/src/Polysemy/Interpretation.hs @@ -0,0 +1,15 @@ +-- | Tools for more advanced usages of 'Polysemy.interpretNew' +module Polysemy.Interpretation + ( -- * Manipuluating effectful state + runExposeH + , runExposeH' + , exposeH + , restoreH + + -- * Lowering Higher-Order thunks to actions of @'Sem' r@. + , Processor(..) + , getProcessorH + , getProcessorH' + ) where + +import Polysemy.Internal.Interpretation diff --git a/src/Polysemy/Membership.hs b/src/Polysemy/Membership.hs index fb6c4f49..c5472082 100644 --- a/src/Polysemy/Membership.hs +++ b/src/Polysemy/Membership.hs @@ -15,4 +15,5 @@ module Polysemy.Membership import Polysemy.Internal import Polysemy.Internal.Combinators +import Polysemy.Internal.Interpretation import Polysemy.Internal.Union diff --git a/src/Polysemy/NonDet.hs b/src/Polysemy/NonDet.hs index 564eb6ab..24cad6c9 100644 --- a/src/Polysemy/NonDet.hs +++ b/src/Polysemy/NonDet.hs @@ -59,12 +59,10 @@ nonDetToError :: Member (Error e) r => e -> Sem (NonDet ': r) a -> Sem r a -nonDetToError (e :: e) = interpretH $ \case +nonDetToError (e :: e) = interpretNew $ \case Empty -> throw e Choose left right -> do - left' <- nonDetToError e <$> runT left - right' <- nonDetToError e <$> runT right - raise (left' `catch` \(_ :: e) -> right') + runH left `catch` \(_ :: e) -> runH right {-# INLINE nonDetToError #-} @@ -106,7 +104,7 @@ instance Monad (NonDetC m) where instance MonadTrans NonDetC where lift m = NonDetC $ \c b -> m >>= (`c` b) -instance MonadTransControl NonDetC where +instance MonadTransWeave NonDetC where type StT NonDetC = [] hoistT n nd = NonDetC $ \c b -> diff --git a/src/Polysemy/Reader.hs b/src/Polysemy/Reader.hs index 615f4925..18829ac1 100644 --- a/src/Polysemy/Reader.hs +++ b/src/Polysemy/Reader.hs @@ -37,11 +37,9 @@ asks f = f <$> ask ------------------------------------------------------------------------------ -- | Run a 'Reader' effect with a constant value. runReader :: i -> Sem (Reader i ': r) a -> Sem r a -runReader i = interpretH $ \case - Ask -> pureT i - Local f m -> do - mm <- runT m - raise $ runReader (f i) mm +runReader i = interpretNew $ \case + Ask -> return i + Local f m -> runReader (f i) (runH' m) {-# INLINE runReader #-} diff --git a/src/Polysemy/Resource.hs b/src/Polysemy/Resource.hs index d613aac0..ca5a731b 100644 --- a/src/Polysemy/Resource.hs +++ b/src/Polysemy/Resource.hs @@ -19,8 +19,10 @@ module Polysemy.Resource ) where import qualified Control.Exception as X +import Control.Monad import Polysemy import Polysemy.Final +import Polysemy.Interpretation ------------------------------------------------------------------------------ @@ -182,34 +184,29 @@ runResource :: ∀ r a . Sem (Resource ': r) a -> Sem r a -runResource = interpretH $ \case +runResource = interpretNew $ \case Bracket alloc dealloc use -> do - a <- runT alloc - d <- bindT dealloc - u <- bindT use - - let run_it = raise . runResource - resource <- run_it a - result <- run_it $ u resource - _ <- run_it $ d resource - pure result + r <- runH alloc + ta <- runExposeH (use r) + -- If "use" failed locally -- which we determine by inspecting + -- the effectful state -- then we run 'dealloc', discarding any + -- changes it does to the local state. + if null ta then do + _ <- runExposeH (dealloc r) + restoreH ta + else do + -- If "use" suceceeded, the we restore it and simply run dealloc as normal. + a <- restoreH ta + _ <- runH (dealloc r) + return a BracketOnError alloc dealloc use -> do - a <- runT alloc - d <- bindT dealloc - u <- bindT use - - let run_it = raise . runResource - - resource <- run_it a - result <- run_it $ u resource - - ins <- getInspectorT - case inspect ins result of - Just _ -> pure result - Nothing -> do - _ <- run_it $ d resource - pure result + r <- runH alloc + ta <- runExposeH (use r) + when (null ta) $ do + _ <- runExposeH (dealloc r) + return () + restoreH ta {-# INLINE runResource #-} diff --git a/src/Polysemy/Writer.hs b/src/Polysemy/Writer.hs index 885a2bab..ee5e3037 100644 --- a/src/Polysemy/Writer.hs +++ b/src/Polysemy/Writer.hs @@ -33,6 +33,7 @@ import Data.Semigroup import Polysemy import Polysemy.Output import Polysemy.State +import Polysemy.Interpretation import Polysemy.Internal.Union import Polysemy.Internal.Writer @@ -66,23 +67,23 @@ runWriter :: Monoid o => Sem (Writer o ': r) a -> Sem r (o, a) -runWriter = runState mempty . reinterpretH +runWriter = runState mempty . reinterpretNew (\case - Tell o -> do - modify' (<> o) >>= pureT + Tell o -> modify' (<> o) Listen m -> do - mm <- runT m - -- TODO(sandy): this is stupid - (o, fa) <- raise $ runWriter mm + -- runExposeH' to prevent local failures from ruining our day + (o, ta) <- runWriter (runExposeH' m) modify' (<> o) - pure $ (o, ) <$> fa + a <- restoreH ta + return (o, a) Pass m -> do - mm <- runT m - (o, t) <- raise $ runWriter mm - ins <- getInspectorT - let f = maybe id fst (inspect ins t) + (o, t) <- runWriter (runExposeH' m) + -- Try to extract the modification function from the t. + -- If "m" failed, default to id. + let f = foldr (const . fst) id t modify' (<> f o) - pure $ snd <$> t + (_, a) <- restoreH t + return a ) {-# INLINE runWriter #-} diff --git a/test/TacticsSpec.hs b/test/TacticsSpec.hs index 91da8934..726be3cc 100644 --- a/test/TacticsSpec.hs +++ b/test/TacticsSpec.hs @@ -15,7 +15,7 @@ interpretTestE = bindTSimple f a spec :: Spec -spec = parallel $ describe "runTH and bindTH" $ do +spec = parallel $ describe "runTSimple and bindTSimple" $ do it "should act as expected" $ do r <- runM (interpretTestE (send (TestE (pure 5) (pure . (9 +))))) print r