polysemy-resume-0.1.0.1: lib/Polysemy/Resume/Resumable.hs
module Polysemy.Resume.Resumable where
import Polysemy.Internal (Sem(Sem), liftSem, raise, raiseUnder, runSem, send)
import Polysemy.Internal.Union (Weaving(Weaving), decomp, hoist, inj, injWeaving, weave)
import Polysemy (Final)
import Polysemy.Error (Error(Throw), catchJust)
import Polysemy.Resume.Data.Resumable (Resumable(..))
import Polysemy.Resume.Data.Stop (Stop, stop)
import Polysemy.Resume.Stop (runStop, stopOnError, stopToIOFinal)
distribEither ::
Functor f =>
f () ->
(f (Either err a) -> res) ->
Either err (f a) ->
res
distribEither initialState result =
result . \case
Right fa -> Right <$> fa
Left err -> Left err <$ initialState
{-# INLINE distribEither #-}
-- |Convert a bare interpreter for @eff@, which (potentially) uses 'Stop' to signal errors, into an interpreter for
-- 'Resumable'.
--
-- >>> run $ resumable interpretStopper (interpretResumer mainProgram)
-- 237
resumable ::
∀ (eff :: Effect) (err :: *) (r :: EffectRow) .
InterpreterFor eff (Stop err : r) ->
InterpreterFor (Resumable err eff) r
resumable interpreter sem =
Sem \ k -> runSem sem \ u ->
case decomp (hoist (resumable interpreter) u) of
Right (Weaving (Resumable e) s wv ex ins) ->
distribEither s ex <$> runSem resultFromEff k
where
resultFromEff =
runStop $ interpreter $ liftSem $ weave s (raise . raise . wv) ins (injWeaving e)
Left g ->
k g
{-# INLINE resumable #-}
-- |Like 'resumable', but use exceptions instead of 'ExceptT'.
resumableIO ::
∀ (eff :: Effect) (err :: *) (r :: EffectRow) .
Typeable err =>
Member (Final IO) r =>
InterpreterFor eff (Stop err : r) ->
InterpreterFor (Resumable err eff) r
resumableIO interpreter sem =
Sem \ k -> runSem sem \ u ->
case decomp (hoist (resumable interpreter) u) of
Right (Weaving (Resumable e) s wv ex ins) ->
distribEither s ex <$> runSem resultFromEff k
where
resultFromEff =
stopToIOFinal $ interpreter $ liftSem $ weave s (raise . raise . wv) ins (injWeaving e)
Left g ->
k g
{-# INLINE resumableIO #-}
-- |Convert an interpreter for @eff@ that uses 'Error' into one using 'Stop' and wrap it using 'resumable'.
resumableError ::
∀ eff err r .
InterpreterFor eff (Error err : Stop err : r) ->
InterpreterFor (Resumable err eff) r
resumableError interpreter =
resumable (stopOnError . interpreter . raiseUnder)
{-# INLINE resumableError #-}
-- |Convert an interpreter for @eff@ that throws errors of type @err@ into a @Resumable@, but limiting the errors
-- handled by consumers to the type @handled@, which rethrowing 'Error's of type @unhandled@.
--
-- The function @canHandle@ determines how the errors are split.
--
-- @
-- newtype Blip =
-- Blip { unBlip :: Int }
-- deriving (Eq, Show)
--
-- bangOnly :: Boom -> Either Text Blip
-- bangOnly = \\case
-- Bang n -> Right (Blip n)
-- Boom msg -> Left msg
--
-- interpretResumerPartial ::
-- Member (Resumable Blip Stopper) r =>
-- InterpreterFor Resumer r
-- interpretResumerPartial =
-- interpret \\ MainProgram ->
-- resume (192 \<$ stopBang) \\ (Blip num) ->
-- pure (num * 3)
-- @
--
-- >>> runError (resumableFor bangOnly interpretStopper (interpretResumerPartial mainProgram))
-- Right 39
resumableOr ::
∀ eff err unhandled handled r .
Member (Error unhandled) r =>
(err -> Either unhandled handled) ->
InterpreterFor eff (Stop err : r) ->
InterpreterFor (Resumable handled eff) r
resumableOr canHandle interpreter sem =
Sem \ k -> runSem sem \ u ->
case decomp (hoist (resumableOr canHandle interpreter) u) of
Right (Weaving (Resumable e) s wv ex ins) ->
distribEither s ex <$> (tryHandle =<< runSem resultFromEff k)
where
tryHandle = \case
Left err ->
either (k . inj . Throw) (pure . Left) (canHandle err)
Right a ->
pure (Right a)
resultFromEff =
runStop $ interpreter $ liftSem $ weave s (raise . raise . wv) ins (injWeaving e)
Left g ->
k g
{-# INLINE resumableOr #-}
-- |Variant of 'resumableOr' that uses 'Maybe' and rethrows the original error.
resumableFor ::
∀ eff err handled r .
Member (Error err) r =>
(err -> Maybe handled) ->
InterpreterFor eff (Stop err : r) ->
InterpreterFor (Resumable handled eff) r
resumableFor canHandle =
resumableOr canHandle'
where
canHandle' err =
maybeToRight err (canHandle err)
{-# INLINE resumableFor #-}
-- |Reinterpreting variant of 'resumableFor'.
catchResumable ::
∀ eff handled err r .
Members [eff, Error err] r =>
(err -> Maybe handled) ->
InterpreterFor (Resumable handled eff) r
catchResumable canHandle sem =
Sem \ k -> runSem sem \ u ->
case decomp (hoist (catchResumable canHandle) u) of
Right (Weaving (Resumable e) s wv ex ins) ->
distribEither s ex <$> runSem resultFromEff k
where
resultFromEff =
catchJust canHandle (fmap Right $ liftSem $ weave s wv ins (injWeaving e)) (pure . Left)
Left g ->
k g
{-# INLINE catchResumable #-}
-- |Interpret an effect @eff@ by wrapping it in @Resumable@ and @Stop@ and leaving the rest up to the user.
runAsResumable ::
∀ err eff r .
Members [Resumable err eff, Stop err] r =>
InterpreterFor eff r
runAsResumable sem =
Sem \ k -> runSem sem \ u ->
case decomp (hoist runAsResumable u) of
Right wav ->
runSem (either stop pure =<< send (Resumable wav)) k
Left g ->
k g
{-# INLINE runAsResumable #-}