{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecursiveDo #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-warnings-deprecations #-} module Control.Cond ( CondT, Cond -- * Executing CondT , runCondT, runCond, execCondT, evalCondT, test -- * Promotions , MonadQuery(..), guardM, guard_, guardM_, apply, consider -- * Basic conditionals , accept, ignore, norecurse, prune -- * Boolean logic , matches, ifM, whenM, unlessM , if_, when_, unless_, or_, and_, not_ -- * helper functions , recurse ) where import Control.Applicative import Control.Arrow (second) import Control.Monad hiding (mapM_, sequence_) import Control.Monad.Base import Control.Monad.Catch import Control.Monad.Cont.Class as C import Control.Monad.Error.Class as E import Control.Monad.Fix import Control.Monad.Morph as M import Control.Monad.Reader.Class as R import Control.Monad.State.Class as S import Control.Monad.Trans import Control.Monad.Trans.Cont (ContT(..)) import Control.Monad.Trans.Control import Control.Monad.Trans.Error (ErrorT(..)) import Control.Monad.Trans.Except (ExceptT(..)) import Control.Monad.Trans.Identity (IdentityT(..)) import Control.Monad.Trans.List (ListT(..)) import Control.Monad.Trans.Maybe (MaybeT(..)) import qualified Control.Monad.Trans.RWS.Lazy as LazyRWS import qualified Control.Monad.Trans.RWS.Strict as StrictRWS import Control.Monad.Trans.Reader (ReaderT(..)) import Control.Monad.Trans.State (StateT(..)) import qualified Control.Monad.Trans.State.Lazy as Lazy import qualified Control.Monad.Trans.State.Strict as Strict import qualified Control.Monad.Trans.Writer.Lazy as Lazy import qualified Control.Monad.Trans.Writer.Strict as Strict import Control.Monad.Writer.Class import Control.Monad.Zip import Data.Foldable import Data.Functor.Identity import Data.Monoid hiding ((<>)) import Data.Semigroup import Prelude hiding (mapM_, foldr1, sequence_) data Recursor a m r = Stop | Recurse (CondT a m r) | Continue deriving Functor instance Semigroup (Recursor a m r) where Stop <> _ = Stop _ <> Stop = Stop Recurse n <> _ = Recurse n _ <> Recurse n = Recurse n _ <> _ = Continue {-# INLINE (<>) #-} instance Monoid (Recursor a m r) where mempty = Continue {-# INLINE mempty #-} mappend = (<>) {-# INLINE mappend #-} instance MFunctor (Recursor a) where hoist _ Stop = Stop hoist nat (Recurse n) = Recurse (hoist nat n) hoist _ Continue = Continue {-# INLINE hoist #-} type CondR a m r = (Maybe r, Recursor a m r) accept' :: r -> CondR a m r accept' x = (Just x, Continue) {-# INLINE accept' #-} recurse' :: CondR a m r recurse' = (Nothing, Continue) {-# INLINE recurse' #-} -- | 'CondT' and its related combinators form a DSL to express whether, given -- an item of type 'a': that item passes the predicate, and/or if recursion -- should be performed from that item, should it relate to the branch of a -- tree. This is used to build predicates that can guide recursive traversals. -- -- For example, when recursing files in a directory tree, there are several -- scenarios that 'CondT' maybe consider: -- -- - Whether the entry at a given path is of interest, independent from its -- type (files or directories) -- - If the path is a directory, if the directory should be recursed into. -- -- Yes or no answers are accepted for either criterion. This means that the -- answer is "no" to both questions for a given directory, the combinator -- 'prune' should be used both to ignore the entry itself, and to prevent -- recursion into its contents. -- -- Several different predicate types may be promoted to 'CondT': -- -- [@Bool@] Using 'guard' -- -- [@m Bool@] Using 'guardM' -- -- [@a -> Bool@] Using 'guard_' -- -- [@a -> m Bool@] Using 'guardM_' -- -- [@a -> m (Maybe r)@] Using 'apply' -- -- [@a -> m (Maybe (r, a))@] Using 'consider' -- -- Here is a trivial example: -- -- @ -- flip runCondT 42 $ do -- guard_ even -- liftIO $ putStrLn "42 must be even to reach here" -- guard_ odd \<|\> guard_ even -- guard_ (== 42) -- @ -- -- If 'CondT' is executed using 'runCondT', it returns a @Maybe r@ if the -- predicate matched. It should usually be run with 'applyCondT', which calls -- a continuation indicating wether recursion should be performed. newtype CondT a m r = CondT { getCondT :: StateT a m (CondR a m r) } deriving Functor type Cond a = CondT a Identity instance (Monad m, Semigroup r) => Semigroup (CondT a m r) where (<>) = liftM2 (<>) {-# INLINE (<>) #-} instance (Monad m, Monoid r) => Monoid (CondT a m r) where mempty = CondT $ return mempty {-# INLINE mempty #-} mappend = liftM2 mappend {-# INLINE mappend #-} instance Monad m => Applicative (CondT a m) where pure = return {-# INLINE pure #-} (<*>) = ap {-# INLINE (<*>) #-} instance Monad m => Monad (CondT a m) where return = CondT . return . accept' {-# INLINE return #-} fail _ = mzero {-# INLINE fail #-} CondT m >>= k = CondT $ m >>= \case (Nothing, Stop) -> return (Nothing, Stop) (Nothing, Continue) -> return (Nothing, Continue) (Nothing, Recurse n) -> return (Nothing, Recurse (n >>= k)) (Just r, Stop) -> fmap (const Stop) `liftM` getCondT (k r) (Just r, Continue) -> getCondT (k r) (Just r, Recurse n) -> getCondT (k r) >>= \case (v, Continue) -> return (v, Recurse (n >>= k)) x -> return x {-# INLINEABLE (>>=) #-} -- #if __GLASGOW_HASKELL__ >= 710 -- {-# SPECIALIZE (>>=) -- :: CondT e IO a -> (a -> CondT e IO b) -> CondT e IO b #-} -- #endif instance MonadReader r m => MonadReader r (CondT a m) where ask = lift R.ask {-# INLINE ask #-} local f (CondT m) = CondT $ R.local f m {-# INLINE local #-} reader = lift . R.reader {-# INLINE reader #-} instance MonadWriter w m => MonadWriter w (CondT a m) where writer = lift . writer {-# INLINE writer #-} tell = lift . tell {-# INLINE tell #-} listen m = m >>= lift . listen . return {-# INLINE listen #-} pass m = m >>= lift . pass . return {-# INLINE pass #-} instance MonadState s m => MonadState s (CondT a m) where get = lift S.get {-# INLINE get #-} put = lift . S.put {-# INLINE put #-} state = lift . S.state {-# INLINE state #-} instance Monad m => Alternative (CondT a m) where empty = CondT $ return recurse' {-# INLINE empty #-} CondT f <|> CondT g = CondT $ do r <- f case r of x@(Just _, _) -> return x _ -> g {-# INLINE (<|>) #-} instance Monad m => MonadPlus (CondT a m) where mzero = CondT $ return recurse' {-# INLINE mzero #-} mplus (CondT f) (CondT g) = CondT $ do r <- f case r of x@(Just _, _) -> return x _ -> g {-# INLINE mplus #-} instance MonadError e m => MonadError e (CondT a m) where throwError = CondT . throwError {-# INLINE throwError #-} catchError (CondT m) h = CondT $ m `catchError` \e -> getCondT (h e) {-# INLINE catchError #-} instance MonadThrow m => MonadThrow (CondT a m) where throwM = CondT . throwM {-# INLINE throwM #-} instance MonadCatch m => MonadCatch (CondT a m) where catch (CondT m) c = CondT $ m `catch` \e -> getCondT (c e) {-# INLINE catch #-} #if MIN_VERSION_exceptions(0,6,0) instance MonadMask m => MonadMask (CondT a m) where #endif mask a = CondT $ mask $ \u -> getCondT (a $ q u) where q u = CondT . u . getCondT {-# INLINE mask #-} uninterruptibleMask a = CondT $ uninterruptibleMask $ \u -> getCondT (a $ q u) where q u = CondT . u . getCondT {-# INLINEABLE uninterruptibleMask #-} instance MonadBase b m => MonadBase b (CondT a m) where liftBase m = CondT $ liftM accept' $ liftBase m {-# INLINE liftBase #-} instance MonadIO m => MonadIO (CondT a m) where liftIO m = CondT $ liftM accept' $ liftIO m {-# INLINE liftIO #-} instance MonadTrans (CondT a) where lift m = CondT $ liftM accept' $ lift m {-# INLINE lift #-} #if MIN_VERSION_monad_control(1,0,0) instance MonadBaseControl b m => MonadBaseControl b (CondT r m) where type StM (CondT r m) a = StM m (CondR r m a, r) liftBaseWith f = CondT $ StateT $ \s -> liftM (\x -> (accept' x, s)) $ liftBaseWith $ \runInBase -> f $ \k -> runInBase $ runStateT (getCondT k) s {-# INLINABLE liftBaseWith #-} restoreM = CondT . StateT . const . restoreM {-# INLINE restoreM #-} #else instance MonadBaseControl b m => MonadBaseControl b (CondT r m) where newtype StM (CondT r m) a = CondTStM { unCondTStM :: StM m (Result r m a, r) } liftBaseWith f = CondT $ StateT $ \s -> liftM (\x -> (accept' x, s)) $ liftBaseWith $ \runInBase -> f $ \k -> liftM CondTStM $ runInBase $ runStateT (getCondT k) s {-# INLINEABLE liftBaseWith #-} restoreM = CondT . StateT . const . restoreM . unCondTStM {-# INLINE restoreM #-} #endif instance MFunctor (CondT a) where hoist nat (CondT m) = CondT $ hoist nat (fmap (hoist nat) `liftM` m) {-# INLINE hoist #-} -- This won't work for StateT-like types -- instance MMonad (CondT a) where -- embed f m = undefined -- {-# INLINE embed #-} instance MonadCont m => MonadCont (CondT a m) where callCC f = CondT $ StateT $ \a -> callCC $ \k -> flip runStateT a $ getCondT $ f $ \r -> CondT $ StateT $ \a' -> k ((Just r, Continue), a') instance Monad m => MonadZip (CondT a m) where mzipWith = liftM2 {-# INLINE mzipWith #-} -- A deficiency of this instance is that recursion uses the same initial 'a'. instance MonadFix m => MonadFix (CondT a m) where mfix f = CondT $ StateT $ \a -> mdo ((mb, n), a') <- case mb of Nothing -> return ((mb, n), a') Just b -> runStateT (getCondT (f b)) a return ((mb, n), a') -- | Apply a condition to an input value, returning a (possibly) updated copy -- of that value if it matches, and the next 'CondT' to use if recursion into -- that value was indicated. runCondT :: Monad m => a -> CondT a m r -> m ((Maybe r, Maybe (CondT a m r)), a) runCondT a c@(CondT (StateT s)) = go `liftM` s a where {-# INLINE go #-} go (p, a') = (second (recursorToMaybe c) p, a') {-# INLINE recursorToMaybe #-} recursorToMaybe _ Stop = Nothing recursorToMaybe p Continue = Just p recursorToMaybe _ (Recurse n) = Just n {-# INLINE runCondT #-} runCond :: a -> Cond a r -> Maybe r runCond = ((fst . fst . runIdentity) .) . runCondT {-# INLINE runCond #-} execCondT :: Monad m => a -> CondT a m r -> m (Maybe a, Maybe (CondT a m r)) execCondT a c = go `liftM` runCondT a c where go ((mr, mnext), a') = (const a' <$> mr, mnext) {-# INLINE execCondT #-} evalCondT :: Monad m => a -> CondT a m r -> m (Maybe r) evalCondT a c = go `liftM` runCondT a c where go ((mr, _), _) = mr {-# INLINE evalCondT #-} -- | A specialized variant of 'runCondT' that simply returns True or False. -- -- >>> let good = guard_ (== "foo.hs") :: Cond String () -- >>> let bad = guard_ (== "foo.hi") :: Cond String () -- >>> runIdentity $ test "foo.hs" $ not_ bad >> return "Success" -- True -- >>> runIdentity $ test "foo.hs" $ not_ good >> return "Shouldn't reach here" -- False test :: Monad m => a -> CondT a m r -> m Bool test a c = go `liftM` runCondT a c where go ((Nothing, _), _) = False go ((Just _, _), _) = True {-# INLINE test #-} -- | 'MonadQuery' is a custom version of 'MonadReader', created so that users -- could still have their own 'MonadReader' accessible within conditionals. class Monad m => MonadQuery a m | m -> a where query :: m a queries :: (a -> b) -> m b update :: a -> m () updates :: (a -> a) -> m () instance Monad m => MonadQuery a (CondT a m) where -- | Returns the item currently under consideration. query = CondT $ gets accept' {-# INLINE query #-} -- | Returns the item currently under consideration while applying a -- function, in the spirit of 'asks'. queries f = CondT $ state (\a -> (accept' (f a), a)) {-# INLINE queries #-} update a = CondT $ liftM accept' $ put a {-# INLINE update #-} updates f = CondT $ liftM accept' $ modify f {-# INLINE updates #-} instance MonadQuery r m => MonadQuery r (ReaderT r m) where query = lift query {-# INLINE query #-} queries = lift . queries {-# INLINE queries #-} update = lift . update {-# INLINE update #-} updates = lift . updates {-# INLINE updates #-} instance (MonadQuery r m, Monoid w) => MonadQuery r (LazyRWS.RWST r w s m) where query = lift query {-# INLINE query #-} queries = lift . queries {-# INLINE queries #-} update = lift . update {-# INLINE update #-} updates = lift . updates {-# INLINE updates #-} instance (MonadQuery r m, Monoid w) => MonadQuery r (StrictRWS.RWST r w s m) where query = lift query {-# INLINE query #-} queries = lift . queries {-# INLINE queries #-} update = lift . update {-# INLINE update #-} updates = lift . updates {-# INLINE updates #-} -- All of these instances need UndecidableInstances, because they do not satisfy -- the coverage condition. instance MonadQuery r' m => MonadQuery r' (ContT r m) where query = lift query {-# INLINE query #-} queries = lift . queries {-# INLINE queries #-} update = lift . update {-# INLINE update #-} updates = lift . updates {-# INLINE updates #-} instance (Error e, MonadQuery r m) => MonadQuery r (ErrorT e m) where query = lift query {-# INLINE query #-} queries = lift . queries {-# INLINE queries #-} update = lift . update {-# INLINE update #-} updates = lift . updates {-# INLINE updates #-} instance MonadQuery r m => MonadQuery r (ExceptT e m) where query = lift query {-# INLINE query #-} queries = lift . queries {-# INLINE queries #-} update = lift . update {-# INLINE update #-} updates = lift . updates {-# INLINE updates #-} instance MonadQuery r m => MonadQuery r (IdentityT m) where query = lift query {-# INLINE query #-} queries = lift . queries {-# INLINE queries #-} update = lift . update {-# INLINE update #-} updates = lift . updates {-# INLINE updates #-} instance MonadQuery r m => MonadQuery r (ListT m) where query = lift query {-# INLINE query #-} queries = lift . queries {-# INLINE queries #-} update = lift . update {-# INLINE update #-} updates = lift . updates {-# INLINE updates #-} instance MonadQuery r m => MonadQuery r (MaybeT m) where query = lift query {-# INLINE query #-} queries = lift . queries {-# INLINE queries #-} update = lift . update {-# INLINE update #-} updates = lift . updates {-# INLINE updates #-} instance MonadQuery r m => MonadQuery r (Lazy.StateT s m) where query = lift query {-# INLINE query #-} queries = lift . queries {-# INLINE queries #-} update = lift . update {-# INLINE update #-} updates = lift . updates {-# INLINE updates #-} instance MonadQuery r m => MonadQuery r (Strict.StateT s m) where query = lift query {-# INLINE query #-} queries = lift . queries {-# INLINE queries #-} update = lift . update {-# INLINE update #-} updates = lift . updates {-# INLINE updates #-} instance (Monoid w, MonadQuery r m) => MonadQuery r (Lazy.WriterT w m) where query = lift query {-# INLINE query #-} queries = lift . queries {-# INLINE queries #-} update = lift . update {-# INLINE update #-} updates = lift . updates {-# INLINE updates #-} instance (Monoid w, MonadQuery r m) => MonadQuery r (Strict.WriterT w m) where query = lift query {-# INLINE query #-} queries = lift . queries {-# INLINE queries #-} update = lift . update {-# INLINE update #-} updates = lift . updates {-# INLINE updates #-} guardM :: MonadPlus m => m Bool -> m () guardM = (>>= guard) {-# INLINE guardM #-} guard_ :: (MonadPlus m, MonadQuery a m) => (a -> Bool) -> m () guard_ f = query >>= guard . f {-# INLINE guard_ #-} guardM_ :: (MonadPlus m, MonadQuery a m) => (a -> m Bool) -> m () guardM_ f = query >>= guardM . f {-# INLINE guardM_ #-} -- | Apply a value-returning predicate. Note that whether or not this return a -- 'Just' value, recursion will be performed in the entry itself, if -- applicable. apply :: (MonadPlus m, MonadQuery a m) => (a -> m (Maybe r)) -> m r apply = queries >=> (>>= maybe mzero return) {-# INLINE apply #-} -- | Consider an element, as 'apply', but returning a mutated form of the -- element. This can be used to apply optimizations to speed future -- conditions. consider :: (MonadPlus m, MonadQuery a m) => (a -> m (Maybe (r, a))) -> m r consider = queries >=> (>>= maybe mzero (\(r, a') -> const r `liftM` update a')) {-# INLINE consider #-} accept :: MonadPlus m => m () accept = return () {-# INLINE accept #-} -- | 'ignore' ignores the current entry, but allows recursion into its -- descendents. This is the same as 'empty'. ignore :: MonadPlus m => m r ignore = mzero {-# INLINE ignore #-} -- | 'norecurse' prevents recursion into the current entry's descendents, but -- does not ignore the entry itself. norecurse :: Monad m => CondT a m () norecurse = CondT $ return (Just (), Stop) {-# INLINE norecurse #-} -- | 'prune' is a synonym for both ignoring an entry and its descendents. prune :: Monad m => CondT a m r prune = CondT $ return (Nothing, Stop) {-# INLINE prune #-} -- | Return True or False depending on whether the given condition matches or -- not. This differs from simply stating the condition in that it itself -- always succeeds. -- -- >>> runCond "foo.hs" $ matches (guard =<< queries (== "foo.hs")) -- Just True -- >>> runCond "foo.hs" $ matches (guard =<< queries (== "foo.hi")) -- Just False matches :: MonadPlus m => m r -> m Bool matches m = (const True `liftM` m) `mplus` return False {-# INLINE matches #-} ifM :: Monad m => m Bool -> m s -> m s -> m s ifM c x y = c >>= \b -> if b then x else y {-# INLINE ifM #-} -- | A variant of ifM which branches on whether the condition succeeds or not. -- Note that @if_ x@ is equivalent to @ifM (matches x)@, and is provided -- solely for convenience. -- -- >>> let good = guard_ (== "foo.hs") :: Cond String () -- >>> let bad = guard_ (== "foo.hi") :: Cond String () -- >>> runCond "foo.hs" $ if_ good (return "Success") (return "Failure") -- Just "Success" -- >>> runCond "foo.hs" $ if_ bad (return "Success") (return "Failure") -- Just "Failure" if_ :: MonadPlus m => m r -> m s -> m s -> m s if_ c x y = matches c >>= \b -> if b then x else y {-# INLINE if_ #-} whenM :: Monad m => m Bool -> m s -> m () whenM c x = ifM c (x >> return ()) (return ()) {-# INLINE whenM #-} -- | 'when_' is just like 'when', except that it executes the body if the -- condition passes, rather than based on a Bool value. -- -- >>> let good = guard_ (== "foo.hs") :: Cond String () -- >>> let bad = guard_ (== "foo.hi") :: Cond String () -- >>> runCond "foo.hs" $ when_ good ignore -- Nothing -- >>> runCond "foo.hs" $ when_ bad ignore -- Just () when_ :: MonadPlus m => m r -> m s -> m () when_ c x = if_ c (x >> return ()) (return ()) {-# INLINE when_ #-} unlessM :: Monad m => m Bool -> m s -> m () unlessM c x = ifM c (return ()) (x >> return ()) {-# INLINE unlessM #-} -- | 'when_' is just like 'when', except that it executes the body if the -- condition fails, rather than based on a Bool value. -- -- >>> let good = guard_ (== "foo.hs") :: Cond String () -- >>> let bad = guard_ (== "foo.hi") :: Cond String () -- >>> runCond "foo.hs" $ unless_ bad ignore -- Nothing -- >>> runCond "foo.hs" $ unless_ good ignore -- Just () unless_ :: MonadPlus m => m r -> m s -> m () unless_ c x = if_ c (return ()) (x >> return ()) {-# INLINE unless_ #-} -- | Check whether at least one of the given conditions is true. This is a -- synonym for 'Data.Foldable.asum'. -- -- >>> let good = guard_ (== "foo.hs") :: Cond String () -- >>> let bad = guard_ (== "foo.hi") :: Cond String () -- >>> runCond "foo.hs" $ or_ [bad, good] -- Just () -- >>> runCond "foo.hs" $ or_ [bad] -- Nothing or_ :: MonadPlus m => [m r] -> m r or_ = Data.Foldable.msum {-# INLINE or_ #-} -- | Check that all of the given conditions are true. This is a synonym for -- 'Data.Foldable.sequence_'. -- -- >>> let good = guard_ (== "foo.hs") :: Cond String () -- >>> let bad = guard_ (== "foo.hi") :: Cond String () -- >>> runCond "foo.hs" $ and_ [bad, good] -- Nothing -- >>> runCond "foo.hs" $ and_ [good] -- Just () and_ :: MonadPlus m => [m r] -> m () and_ = sequence_ {-# INLINE and_ #-} -- | 'not_' inverts the meaning of the given predicate. -- -- >>> let good = guard_ (== "foo.hs") :: Cond String () -- >>> let bad = guard_ (== "foo.hi") :: Cond String () -- >>> runCond "foo.hs" $ not_ bad >> return "Success" -- Just "Success" -- >>> runCond "foo.hs" $ not_ good >> return "Shouldn't reach here" -- Nothing not_ :: MonadPlus m => m r -> m () not_ c = if_ c ignore accept {-# INLINE not_ #-} -- | 'recurse' changes the recursion predicate for any child elements. For -- example, the following file-finding predicate looks for all @*.hs@ files, -- but under any @.git@ directory looks only for a file named @config@: -- -- @ -- if_ (name_ \".git\" \>\> directory) -- (ignore \>\> recurse (name_ \"config\")) -- (glob \"*.hs\") -- @ -- -- NOTE: If this code had used @recurse (glob \"*.hs\"))@ instead in the else -- case, it would have meant that @.git@ is only looked for at the top-level -- of the search (i.e., the top-most element). recurse :: Monad m => CondT a m r -> CondT a m r recurse c = CondT $ fmap (const (Recurse c)) `liftM` getCondT c {-# INLINE recurse #-}