BPS-0.1.0.0: src/main/haskell/lib/Data/TMS/Helpers.hs
{-|
Module : Helpers
Description : Helping functions, sort of ExtraExtra
Copyright : (c) John Maraist, 2022
License : AllRightsReserved
Maintainer : haskell-tms@maraist.org
Stability : experimental
Portability : POSIX
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, for NON-COMMERCIAL use. See the License for the specific
language governing permissions and limitations under the License.
-}
{-# LANGUAGE RankNTypes #-}
module Data.TMS.Helpers where
import Control.Monad.State
import Control.Monad.ST.Trans
import Control.Monad.Except
import Control.Monad.Extra
import Data.List
-- * Lists and monads
-- | Check whether a list contains a value which, when applied to a
-- computation, returns @True@.
anyByM :: (Monad m) => (a -> m Bool) -> [a] -> m Bool
anyByM _ [] = return False
anyByM k (x : xs) = do
b <- k x
if b then return True else anyByM k xs
-- | Check whether a returned list contains a value which satisfies
-- some monadic predicate.
anyMM :: (Monad m) => (a -> m Bool) -> m [a] -> m Bool
anyMM predM srcM = do
src <- srcM
anyByM predM src
-- | Check whether all of the values of a list, when applied to a
-- computation, return @True@.
allByM :: (Monad m) => (a -> m Bool) -> [a] -> m Bool
allByM _ [] = return True
allByM k (x : xs) = do
b <- k x
if b then allByM k xs else return False
-- * Ordered lists
-- | Determine whether one list is a subset of the other, under the
-- assumption that both lists are sorted in ascending order.
ordSubsetp :: Ord a => [a] -> [a] -> Bool
ordSubsetp [] _ = True
ordSubsetp (_ : _) [] = False
ordSubsetp l1@(n1 : ns1) l2@(n2 : ns2) =
case compare n1 n2 of
LT -> False
EQ -> ordSubsetp ns1 ns2
GT -> ordSubsetp l1 ns2
-- * Even more loops
-- | Convert a list to a string, where the converter for each element
-- is a monadic computation.
formatList :: Monad m => String -> (a -> m String) -> [a] -> m String
formatList s f xs = mapM f xs >>= return . intercalate s
-- | Like `forM_`, but with both the elements source as well as the
-- loop body as computations over the monad.
forMM_ :: (Monad m, Foldable t) => m (t a) -> (a -> m ()) -> m ()
forMM_ srcM f = do
src <- srcM
forM_ src f
-- | A @while@ loop, guard at the top.
whileDo :: Monad m => m Bool -> m () -> m ()
whileDo cond body =
cond >>= \b -> if b then body >> whileDo cond body else return ()
-- | A @while@ loop based on stuff, guard at the top.
whileDoWith :: Monad m => m a -> (a -> Bool) -> (a -> m ()) -> m ()
whileDoWith src predicate body = do
val <- src
if predicate val then (do body val
whileDoWith src predicate body)
else return ()
-- | Like `forM_`, but with an extra check run after the body of the
-- loop. If the check fails, the loop exits early.
forMwhile_ :: Monad m => [a] -> m Bool -> (a -> m ()) -> m ()
forMwhile_ [] _ _ = return ()
forMwhile_ (x : xs) pred bodyf = do
whenM pred $ do
bodyf x
forMwhile_ xs pred bodyf
-- | Like `forMwhile_`, but the source list is also the result of a
-- monadic computation.
forMMwhile_ :: Monad m => m [a] -> m Bool -> (a -> m ()) -> m ()
forMMwhile_ xsM condM bodyf = do
xs <- xsM
forMwhile_ xs condM bodyf
-- | Like `forMM_`, except instead of a fixed list, loop over `Maybe`
-- values returned from a subcomputation, until that subcomputation
-- returns `Nothing`.
whileReturnJust :: Monad m => m (Maybe a) -> (a -> m ()) -> m ()
whileReturnJust gen f = do
res <- gen
case res of
Nothing -> return ()
Just x -> do
f x
whileReturnJust gen f
-- | Like `unless`, expect both the tested value and the body are
-- returned from a computation in a monad.
unlessMM :: Monad m => m Bool -> m () -> m ()
unlessMM cnd body = whenM (notM cnd) body
-- * Lists under references in the `STT` monad transformer
-- |Monadic version of @null@ for a list stored in an `STRef`: returns
-- `True` when the list is empty.
nullR :: Monad m => STRef s [a] -> STT s m Bool
nullR ref = do
xs <- readSTRef ref
return $ null xs
-- |Opposite of `nullR`, returning `False` when the referenced list is
-- empty.
nonnullR :: Monad m => STRef s [a] -> STT s m Bool
nonnullR ref = do
xs <- readSTRef ref
return $ not $ null xs
-- |Like a combination of `whenM` and `nonnullR`, where the body
-- receives the (pure) non-null list as an argument.
whenNonnullR :: (Monad m0, Monad m) =>
(forall r . STT s m0 r -> m r) -> STRef s [a] -> ([a] -> m ()) -> m ()
whenNonnullR lifter ref bodyf = do
xs <- lifter $ readSTRef ref
if (null xs) then return () else bodyf xs
-- |Map over the values contained within a list of references.
mapRefs :: Monad m => (a -> b) -> [STRef s a] -> STT s m [b]
mapRefs f [] = return []
mapRefs f (xr : xrs) = do
x <- readSTRef xr
xs' <- mapRefs f xrs
return $ f x : xs'
-- |Fold (right-associatively) the values contained within a list of
-- references.
foldrRefs :: Monad m => (a -> b -> b) -> b -> [STRef s a] -> STT s m b
foldrRefs f z [] = return z
foldrRefs f z (xr : xrs) = do
x <- readSTRef xr
z' <- foldrRefs f z xrs
return $ f x z'
-- |Fold (left-associatively) the values contained within a list of
-- references.
foldlRefs :: Monad m => (b -> a -> b) -> b -> [STRef s a] -> STT s m b
foldlRefs f z [] = return z
foldlRefs f z (xr : xrs) = do
x <- readSTRef xr
foldlRefs f (f z x) xrs
-- | Like `forM_`, but with the list under an `STRef`. The first
-- argument lifts an `STT` operation into @m@.
forRM_ ::
(Monad m, Monad m0, Foldable t) =>
(STT s m0 (t a) -> m (t a)) ->
STRef s (t a) -> (a -> m ()) -> m ()
forRM_ liftSTT srcR f = do
src <- liftSTT $ readSTRef srcR
forM_ src f
-- ** Stack-like operations
-- |Push a value onto the front of the list at the given `STT`
-- reference.
push :: Monad m => a -> STRef s [a] -> STT s m ()
push v r = do
prev <- readSTRef r
writeSTRef r $ v : prev
-- |Push the result of a computation onto the front of the list at the
-- given `STT` reference.
pushM :: Monad m => m a -> STRef s [a] -> STT s m ()
pushM m r = do
v <- lift m
push v r
-- |Push every value in a collection onto the front of the list at the
-- given `STT` reference.
pushAll :: (Monad m, Traversable t) => t a -> STRef s [a] -> STT s m ()
pushAll vs r = forM_ vs $ \v -> push v r
-- |Push every value in a collection returned from a computation onto
-- the front of the list at the given `STT` reference.
pushAllM :: (Monad m, Traversable t) => m (t a) -> STRef s [a] -> STT s m ()
pushAllM m r = do
vs <- lift m
pushAll vs r
-- |Pop a value from the given reference to a list if one exists.
pop :: Monad m => STRef s [a] -> STT s m (Maybe a)
pop queue = do
queueList <- readSTRef queue
case queueList of
[] -> return Nothing
(x : xs) -> do
writeSTRef queue xs
return $ Just x
-- |Consumes the elements of a referenced list, one at a time, until
-- the list is empty. The first argument is a @lift@-style function
-- which brings `STT` operations into the top-level monad of interest.
-- Intended to be compatible with stack-like behavior (such as with
-- `push`; this function does use `pop`) where the body of the loop
-- may add elements.
whileListM_ :: (Monad m0, Monad m) =>
(forall r . STT s m0 r -> m r) -> STRef s [a] -> (a -> m ()) -> m ()
whileListM_ lifter listRef bodyf = whileListM_'
where whileListM_' = do
top <- lifter $ pop listRef
case top of
Nothing -> return ()
Just x -> do
bodyf x
whileListM_'
-- * Strings
-- |Form a comma-separated string from a list.
commaList :: (a -> String) -> [a] -> String
commaList f [] = ""
commaList f xs = foldl1 (\ x y -> x ++ ", " ++ y) $ map f xs
-- |Remove the `Just` constructors from the elements of a list,
-- discarding elements which are `Nothing`.
unmaybe [] = []
unmaybe (Just a : xs) = a : unmaybe xs
unmaybe (_ : xs) = unmaybe xs