packages feed

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