-- | General purpose proxies module Control.Proxy.Prelude.Base ( -- * Maps mapB, mapD, mapU, mapMB, mapMD, mapMU, execB, execD, execU, -- * Filters takeB, takeB_, takeWhileD, takeWhileU, dropD, dropU, dropWhileD, dropWhileU, filterD, filterU, -- * Lists fromListS, fromListC, -- * Enumerations enumFromS, enumFromC, enumFromToS, enumFromToC ) where import Control.Monad (replicateM_, void, when, (>=>)) import Control.Monad.Trans.Class (lift) import Control.Proxy.Class (request, respond, idT) import Control.Proxy.Core (Proxy, Server, Client) import Control.Proxy.Prelude.Kleisli (foreverK, replicateK) {-| @(mapB f g)@ applies @f@ to all values going downstream and @g@ to all values going upstream. Mnemonic: map \'@B@\'idirectional > mapB f1 g1 >-> mapB f2 g2 = mapB (f2 . f1) (g1 . g2) > > mapB id id = idT -} mapB :: (Monad m) => (a -> b) -> (b' -> a') -> b' -> Proxy a' a b' b m r mapB f g = foreverK $ (request . g) >=> (respond . f) {-| @(mapD f)@ applies @f@ to all values going \'@D@\'ownstream. > mapD f1 >-> mapD f2 = mapD (f2 . f1) > > mapD id = idT -} mapD :: (Monad m) => (a -> b) -> x -> Proxy x a x b m r mapD f = foreverK $ request >=> (respond . f) {-| @(mapU g)@ applies @g@ to all values going \'@U@\'pstream. > mapU g1 >-> mapU g2 = mapU (g1 . g2) > > mapU id = idT -} mapU :: (Monad m) => (b' -> a') -> b' -> Proxy a' x b' x m r mapU g = foreverK $ (request . g) >=> respond {-| @(mapMB f g)@ applies the monadic function @f@ to all values going downstream and the monadic function @g@ to all values going upstream. > mapMB f1 g1 >-> mapMB f2 g2 = mapMB (f1 >=> f2) (g2 >=> g1) > > mapMB return return = idT -} mapMB :: (Monad m) => (a -> m b) -> (b' -> m a') -> b' -> Proxy a' a b' b m r mapMB f g = foreverK $ lift . g >=> request >=> lift . f >=> respond {-| @(mapMD f)@ applies the monadic function @f@ to all values going downstream > mapMD f1 >-> mapMD f2 = mapMD (f1 >=> f2) > > mapMD return = idT -} mapMD :: (Monad m) => (a -> m b) -> x -> Proxy x a x b m r mapMD f = foreverK $ request >=> lift . f >=> respond {-| @(mapMU g)@ applies the monadic function @g@ to all values going upstream > mapMU g1 >-> mapMU g2 = mapMU (g2 >=> g1) > > mapMU return = idT -} mapMU :: (Monad m) => (b' -> m a') -> b' -> Proxy a' x b' x m r mapMU g = foreverK $ lift . g >=> request >=> respond {-| @(execB md mu)@ executes @mu@ every time values flow upstream through it, and executes @md@ every time values flow downstream through it. > execB md1 mu1 >-> execB md2 mu2 = execB (md1 >> md2) (mu2 >> mu1) > > execB (return ()) = idT -} execB :: (Monad m) => m () -> m () -> a' -> Proxy a' a a' a m r execB md mu = foreverK $ \a' -> do lift mu a <- request a' lift md respond a {-| @execD md)@ executes @md@ every time values flow downstream through it. > execD md1 >-> execD md2 = execD (md1 >> md2) > > execD (return ()) = idT -} execD :: (Monad m) => m () -> a' -> Proxy a' a a' a m r execD md = foreverK $ \a' -> do a <- request a' lift md respond a {-| @execU mu)@ executes @mu@ every time values flow upstream through it. > execU mu1 >-> execU mu2 = execU (mu2 >> mu1) > > execU (return ()) = idT -} execU :: (Monad m) => m () -> a' -> Proxy a' a a' a m r execU mu = foreverK $ \a' -> do lift mu a <- request a' respond a {-| @(takeB n)@ allows @n@ upstream/downstream roundtrips to pass through > takeB n1 >=> takeB n2 = takeB (n1 + n2) -- n1 >= 0 && n2 >= 0 > > takeB 0 = return -} takeB :: (Monad m) => Int -> a' -> Proxy a' a a' a m a' takeB n = replicateK n $ request >=> respond -- | 'takeB_' is 'takeB' with a @()@ return value, convenient for composing takeB_ :: (Monad m) => Int -> a' -> Proxy a' a a' a m () takeB_ n = fmap void (takeB n) {-| @takeWhileD p@ allows values to pass downstream so long as they satisfy the predicate @p@. > -- Using the "All" monoid over functions: > mempty = \_ -> True > (p1 <> p2) a = p1 a && p2 a > > takeWhileD p1 >-> takeWhileD p2 = takeWhileD (p1 <> p2) > > takeWhileD mempty = idT -} takeWhileD :: (Monad m) => (a -> Bool) -> a' -> Proxy a' a a' a m () takeWhileD p = go where go a' = do a <- request a' if (p a) then do a'2 <- respond a go a'2 else return () {-| @takeWhileU p@ allows values to pass upstream so long as they satisfy the predicate @p@. > takeWhileU p1 >-> takeWhileU p2 = takeWhileU (p1 <> p2) > > takeWhileD mempty = idT -} takeWhileU :: (Monad m) => (a' -> Bool) -> a' -> Proxy a' a a' a m () takeWhileU p = go where go a' = if (p a') then do a <- request a' a'2 <- respond a go a'2 else return () {-| @(dropD n)@ discards @n@ values going downstream > dropD n1 >-> dropD n2 = dropD (n1 + n2) -- n2 >= 0 && n2 >= 0 > > dropD 0 = idT -} dropD :: (Monad m) => Int -> () -> Proxy () a () a m r dropD n () = do replicateM_ n $ request () idT () {-| @(dropU n)@ discards @n@ values going upstream > dropU n1 >-> dropU n2 = dropU (n1 + n2) -- n2 >= 0 && n2 >= 0 > > dropU 0 = idT -} dropU :: (Monad m) => Int -> a' -> Proxy a' () a' () m r dropU n a' | n <= 0 = idT a' | otherwise = do replicateM_ (n - 1) $ respond () a'2 <- respond () idT a'2 {-| @(dropWhileD p)@ discards values going upstream until one violates the predicate @p@. > -- Using the "Any" monoid over functions: > mempty = \_ -> False > (p1 <> p2) a = p1 a || p2 a > > dropWhileD p1 >-> dropWhileD p2 = dropWhileD (p1 <> p2) > > dropWhileD mempty = idT -} dropWhileD :: (Monad m) => (a -> Bool) -> () -> Proxy () a () a m r dropWhileD p () = go where go = do a <- request () if (p a) then go else do respond a idT () {-| @(dropWhileU p)@ discards values going downstream until one violates the predicate @p@. > dropWhileU p1 >-> dropWhileU p2 = dropWhileU (p1 <> p2) > > dropWhileU mempty = idT -} dropWhileU :: (Monad m) => (a' -> Bool) -> a' -> Proxy a' () a' () m r dropWhileU p = go where go a' = if (p a') then do a' <- respond () go a' else idT a' {-| @(filterD p)@ discards values going downstream if they fail the predicate @p@ > -- Using the "All" monoid over functions: > mempty = \_ -> True > (p1 <> p2) a = p1 a && p2 a > > filterD p1 >-> filterD p2 = filterD (p1 <> p2) > > filterD mempty = idT -} filterD :: (Monad m) => (a -> Bool) -> () -> Proxy () a () a m r filterD p () = go where go = do a <- request () when (p a) $ respond a go {-| @(filterU p)@ discards values going upstream if they fail the predicate @p@ > filterU p1 >-> filterU p2 = filterU (p1 <> p2) > > filterU mempty = idT -} filterU :: (Monad m) => (a' -> Bool) -> a' -> Proxy a' () a' () m r filterU p a' = go a' where go a' = do when (p a') $ request a' a'2 <- respond () go a'2 {-| Convert a list into a 'Server' > fromListS xs >=> fromListS ys = fromListS (xs ++ ys) > > fromListS [] = return -} fromListS :: (Monad m) => [a] -> () -> Server () a m () fromListS xs () = mapM_ respond xs {-| Convert a list into a 'Client' > fromListC xs >=> fromListC ys = fromListC (xs ++ ys) > > fromListC [] = return -} fromListC :: (Monad m) => [a] -> () -> Client a () m () fromListC xs () = mapM_ request xs -- | 'Server' version of 'enumFrom' enumFromS :: (Enum a, Monad m) => a -> () -> Server () a m r enumFromS a () = go a where go a = do respond a go (succ a) -- | 'Client' version of 'enumFrom' enumFromC :: (Enum a, Monad m) => a -> () -> Client a () m r enumFromC a () = go a where go a = do request a go (succ a) -- | 'Server' version of 'enumFromTo' enumFromToS :: (Enum a, Ord a, Monad m) => a -> a -> () -> Server () a m () enumFromToS a1 a2 () = go a1 where go n | n > a2 = return () | otherwise = do respond n go (succ n) -- | 'Client' version of 'enumFromTo' enumFromToC :: (Enum a, Ord a, Monad m) => a -> a -> () -> Client a () m () enumFromToC a1 a2 () = go a1 where go n | n > a2 = return () | otherwise = do request n go (succ n)