packages feed

lattest-lib-0.1.0.0: src/Lattest/Util/IOUtils.hs

{- |
    An unsorted collection of utility functions for working with the IO monad.
-}
module Lattest.Util.IOUtils (
-- * Control flow
ifM_,
ifMM_,
ifM,
ifMM,
whileM,
transformWhile,
doMaybe,
-- * STM Control flow
waitUntil,
doAfter,
-- * Encoding state in IO monads
statefulIO,
statefulIO'
)
where

import Control.Monad (void, unless, when)
import GHC.Conc(newTVarIO, readTVar, writeTVar, atomically, STM, retry)

-- | While-loop for monads. Any state passed between loop iterations should be carried by the monad.
whileM :: (Monad m) => m Bool -> m () -> m ()
whileM mb m = ifMM_ mb (m >> whileM mb m)

-- | If-statement for monads, with a monadic condition: if the condition holds, execute the given monad and discard the result, otherwise return ().
ifMM_ :: (Monad m) => m Bool -> m a -> m ()
ifMM_ mb m = do
    b <- mb
    ifM_ b m

-- | If-statement for monads, with a pure condition: if the condition holds, execute the given monad and discard the result, otherwise return ().
ifM_ :: (Applicative m) => Bool -> m a -> m ()
ifM_ b m = when b $ void m

-- | If-statement for monads, with a monadic condition: if the condition holds, execute the given monad and return the result, otherwise return Nothing.
ifMM :: (Monad m) => m Bool -> m a -> m (Maybe a)
ifMM mb m = do
    b <- mb
    ifM b m

-- | If-statement for monads, with a pure condition: if the condition holds, execute the given monad and return the result, otherwise return Nothing.
ifM :: Applicative m => Bool -> m a -> m (Maybe a)
ifM b m = if b then Just <$> m else pure Nothing

-- | Execute the given Monad, or return () if not present.
doMaybe :: Applicative m => Maybe (m ()) -> m ()
doMaybe (Just m) = m
doMaybe Nothing = pure ()

-- | While-loop for monads with explicit state passing.
transformWhile :: (Monad m) => a -> (a -> m (a, Bool)) -> m a
transformWhile state transformAndPredicate = do
    (state', condition) <- transformAndPredicate state
    if condition
        then transformWhile state' transformAndPredicate
        else return state'

-- | Produce an IO monad that behaves like the given Mealy machine, with separate transition and output functions.
statefulIO :: (q -> i -> q) -> (q -> i -> o) -> q -> IO (i -> IO o)
statefulIO transitionFunction outputFunction = statefulIO' (\q i -> (transitionFunction q i, outputFunction q i))

-- | Produce an IO monad that behaves like the given Mealy machine, with a combined transition/output function.
statefulIO' :: (q -> i -> (q, o)) -> q -> IO (i -> IO o)
statefulIO' transitionFunction initialState = do
    stateRef <- newTVarIO initialState
    return $ \i -> atomically $ do
        state <- readTVar stateRef
        let (state', output) = transitionFunction state i
        writeTVar stateRef state'
        return output

-- | An STM action that waits for the given STM condition
waitUntil :: STM Bool -> STM ()
waitUntil condition = do
    mayContinue <- condition
    unless mayContinue retry

-- | An STM action that waits for the given STM condition, and then performs the given STM action
doAfter :: STM Bool -> STM a -> STM a
doAfter condition action = do
    waitUntil condition
    action