automaton-1.3: src/Data/Automaton/Trans/Maybe.hs
-- | An 'Automaton' with 'Maybe' or 'MaybeT' in its monad stack can terminate execution at any step.
module Data.Automaton.Trans.Maybe (
module Data.Automaton.Trans.Maybe,
module Control.Monad.Trans.Maybe,
maybeToExceptS,
)
where
-- base
import Control.Arrow (arr, returnA, (>>>))
-- transformers
import Control.Monad.Trans.Maybe hiding (
liftCallCC,
liftCatch,
liftListen,
liftPass,
)
-- automaton
import Data.Automaton (Automaton, arrM, constM, hoistS, liftS)
import Data.Automaton.Trans.Except (
ExceptT,
exceptS,
listToAutomatonExcept,
maybeToExceptS,
reactimateExcept,
runAutomatonExcept,
runExceptT,
safe,
safely,
try,
)
-- * Throwing 'Nothing' as an exception ("exiting")
-- | Throw the exception immediately.
exit :: (Monad m) => Automaton (MaybeT m) a b
exit = constM $ MaybeT $ return Nothing
-- | Throw the exception when the condition becomes true on the input.
exitWhen :: (Monad m) => (a -> Bool) -> Automaton (MaybeT m) a a
exitWhen condition = proc a -> do
_ <- exitIf -< condition a
returnA -< a
-- | Exit when the incoming value is 'True'.
exitIf :: (Monad m) => Automaton (MaybeT m) Bool ()
exitIf = proc condition ->
if condition
then exit -< ()
else returnA -< ()
-- | @Just a@ is passed along, 'Nothing' causes the whole 'Automaton' to exit.
maybeExit :: (Monad m) => Automaton (MaybeT m) (Maybe a) a
maybeExit = inMaybeT
-- | Embed a 'Maybe' value in the 'MaybeT' layer. Identical to 'maybeExit'.
inMaybeT :: (Monad m) => Automaton (MaybeT m) (Maybe a) a
inMaybeT = arrM $ MaybeT . return
-- * Catching Maybe exceptions
-- | Run the first automaton until the second one produces 'True' from the output of the first.
untilMaybe :: (Monad m) => Automaton m a b -> Automaton m b Bool -> Automaton (MaybeT m) a b
untilMaybe automaton cond = proc a -> do
b <- liftS automaton -< a
c <- liftS cond -< b
inMaybeT -< if c then Nothing else Just b
{- | When an exception occurs in the first 'automaton', the second 'automaton' is executed
from there.
-}
catchMaybe ::
(Functor m, Monad m) =>
Automaton (MaybeT m) a b ->
Automaton m a b ->
Automaton m a b
catchMaybe automaton1 automaton2 = safely $ try (maybeToExceptS automaton1) >> safe automaton2
-- * Converting to and from 'MaybeT'
-- | Convert exceptions into `Nothing`, discarding the exception value.
exceptToMaybeS ::
(Functor m, Monad m) =>
Automaton (ExceptT e m) a b ->
Automaton (MaybeT m) a b
exceptToMaybeS =
hoistS $ MaybeT . fmap (either (const Nothing) Just) . runExceptT
{- | Converts a list to an 'Automaton' in 'MaybeT', which outputs an element of the
list at each step, throwing 'Nothing' when the list ends.
-}
listToMaybeS :: (Functor m, Monad m) => [b] -> Automaton (MaybeT m) a b
listToMaybeS = exceptToMaybeS . runAutomatonExcept . listToAutomatonExcept
-- * Running 'MaybeT'
{- | Remove the 'MaybeT' layer by outputting 'Nothing' when the exception occurs.
The current state is then tested again on the next input.
-}
runMaybeS :: (Functor m, Monad m) => Automaton (MaybeT m) a b -> Automaton m a (Maybe b)
runMaybeS automaton = exceptS (maybeToExceptS automaton) >>> arr eitherToMaybe
where
eitherToMaybe (Left ()) = Nothing
eitherToMaybe (Right b) = Just b
-- | 'reactimate's an 'Automaton' in the 'MaybeT' monad until it throws 'Nothing'.
reactimateMaybe ::
(Functor m, Monad m) =>
Automaton (MaybeT m) () () ->
m ()
reactimateMaybe automaton = reactimateExcept $ try $ maybeToExceptS automaton
{- | Run an 'Automaton' fed from a list, discarding results. Useful when one needs to
combine effects and streams (i.e., for testing purposes).
-}
embed_ :: (Functor m, Monad m) => Automaton m a () -> [a] -> m ()
embed_ automaton as = reactimateMaybe $ listToMaybeS as >>> liftS automaton