{-| A 'Proxy' 'request's input from upstream and 'respond's with output to downstream. For an extended tutorial, consult "Control.Proxy.Tutorial". -} module Control.Proxy.Core ( -- * Types -- $types ProxyF(..), Proxy(..), C, Server, Client, Session, -- * Run Sessions -- $run runProxy, runProxyK, runSession, runSessionK, -- * Utility Proxies -- $utility discard, ignore ) where import Control.Applicative (Applicative(pure, (<*>))) import Control.Monad (ap, forever, liftM, (>=>)) import Control.Monad.IO.Class (MonadIO(liftIO)) import Control.Monad.Trans.Class (MonadTrans(lift)) import Control.Monad.Trans.Free ( FreeF(Free, Pure), FreeT(FreeT, runFreeT), liftF, hoistFreeT, wrap ) import Control.MFunctor (MFunctor(mapT)) import Control.Proxy.Class ( Channel(idT, (<-<)), Interact(request, (//)) ) import Data.Closed (C) {- $types A 'Proxy' communicates with an upstream interface and a downstream interface. The type variables of @Proxy req_a resp_a req_b resp_b m r@ signify: * @req_a @ - The request supplied to the upstream interface * @resp_a@ - The response provided by the upstream interface * @req_b @ - The request supplied by the downstream interface * @resp_b@ - The response provided to the downstream interface * @m @ - The base monad * @r @ - The final return value -} -- | The base functor for the 'Proxy' type data ProxyF a' a b' b x = Request a' (a -> x) | Respond b (b' -> x) instance Functor (ProxyF a' a b' b) where fmap f (Respond b fb') = Respond b (f . fb') fmap f (Request a' fa ) = Request a' (f . fa ) -- | A 'Proxy' converts one interface to another newtype Proxy a' a b' b m r = Proxy { unProxy :: FreeT (ProxyF a' a b' b) m r } instance (Monad m) => Functor (Proxy a' a b' b m) where fmap = liftM instance (Monad m) => Applicative (Proxy a' a b' b m) where pure = return (<*>) = ap instance (Monad m) => Monad (Proxy a' a b' b m) where return = Proxy . return m >>= f = Proxy $ unProxy m >>= unProxy . f instance MonadTrans (Proxy a' a b' b) where lift = Proxy . lift instance (MonadIO m) => MonadIO (Proxy a' a b' b m) where liftIO = Proxy . liftIO instance Channel Proxy where idT = Proxy . idT' p1 <-< p2 = Proxy . ((unProxy . p1) <- a' -> FreeT (ProxyF a' a a' a) m r idT' a' = wrap $ Request a' $ \a -> wrap $ Respond a idT' (<- (c' -> FreeT (ProxyF b' b c' c) m r) -> (b' -> FreeT (ProxyF a' a b' b) m r) -> (c' -> FreeT (ProxyF a' a c' c) m r) p1 <- FreeT $ do x1 <- runFreeT $ p1 c' runFreeT $ case x1 of Pure r -> return r Free (Respond c fc') -> wrap $ Respond c (fc' <- FreeT $ do x2 <- runFreeT $ p2 b' runFreeT $ case x2 of Pure r -> return r Free (Respond b fb') -> ((\_ -> fb b) <- do let p1' = \_ -> FreeT $ return x1 wrap $ Request a' $ \a -> (p1' <- fa a)) c' instance Interact Proxy where request a' = Proxy $ liftF $ Request a' id p1 // p2 = (Proxy .) $ (unProxy . p1) />/? (unProxy . p2) (/ (c' -> FreeT (ProxyF b' b x' x) m c) -> (b' -> FreeT (ProxyF a' a x' x) m b) -> (c' -> FreeT (ProxyF a' a x' x) m c) f1 / FreeT $ do x1 <- runFreeT $ f1 a' runFreeT $ case x1 of Pure a -> return a Free (Respond x fx') -> wrap $ Respond x $ fx' / (f2 >=> (fb //?) :: (Monad m) => (a -> FreeT (ProxyF x' x b' b) m a') -> (b -> FreeT (ProxyF x' x c' c) m b') -> (a -> FreeT (ProxyF x' x c' c) m a') f1 />/? f2 = \a' -> FreeT $ do x1 <- runFreeT $ f1 a' runFreeT $ case x1 of Pure a' -> return a' Free (Respond b fb') -> (f2 >=> (fb' />/? f2)) b Free (Request x' fx ) -> wrap $ Request x' $ fx />/? f2 instance MFunctor (Proxy a' a b' b) where mapT nat = Proxy . hoistFreeT nat . unProxy {-| @Server req resp@ receives requests of type @req@ and sends responses of type @resp@. 'Server's only 'respond' and never 'request' anything. -} type Server req resp = Proxy C () req resp {-| @Client req resp@ sends requests of type @req@ and receives responses of type @resp@. 'Client's only 'request' and never 'respond' to anything. -} type Client req resp = Proxy req resp () C {-| A self-contained 'Session', ready to be run by 'runSession' 'Session's never 'request' anything or 'respond' to anything. -} type Session = Proxy C () () C {- $run I provide two ways to run proxies: * 'runProxy', which discards unhandled output from either end * 'runSession', which type restricts its argument to ensure no loose ends Both functions require that the input to each end is trivially satisfiable, (i.e. @()@). I recommend 'runProxy' for most use cases since it is more convenient. 'runSession' only accepts sessions that do not send unhandled data flying off each end, which provides the following benefits: * It prevents against accidental data loss. * It protects against silent failures * It prevents wastefully draining a scarce resource by gratuitously driving it to completion However, this restriction means that you must either duplicate every utility function to specialize them to the end-point positions (which I do not do), or explicitly close loose ends using the 'discard' and 'ignore' proxies: > runSession $ discard <-< p <-< ignore Use the \'@K@\' versions of each command if you are running sessions nested within sessions. They provide a Kleisli arrow as their result suitable to be passed to another 'runProxy' / 'runSession' command. -} {-| Run a self-sufficient 'Proxy' Kleisli arrow, converting it back to the base monad -} runProxy :: (Monad m) => (() -> Proxy a () () b m r) -> m r runProxy p = runProxyK p () {-| Run a self-sufficient 'Proxy' Kleisli arrow, converting it back to a Kleisli arrow in the base monad -} runProxyK :: (Monad m) => (() -> Proxy a () () b m r) -> (() -> m r) runProxyK p = runProxy' . unProxy . p runProxy' :: (Monad m) => FreeT (ProxyF a () () b) m r -> m r runProxy' p = do x <- runFreeT p case x of Pure r -> return r Free (Respond _ fb ) -> runProxy' $ fb () Free (Request _ fa') -> runProxy' $ fa' () {-| Run a self-contained 'Session' Kleisli arrow, converting it back to the base monad -} runSession :: (Monad m) => (() -> Session m r) -> m r runSession = runProxy {-| Run a self-contained 'Session' Kleisli arrow, converting it back to a Kleisli arrow in the base monad -} runSessionK :: (Monad m) => (() -> Session m r) -> (() -> m r) runSessionK = runProxyK {- $utility 'discard' provides a fallback client that gratuitously 'request's input from a server, but discards all responses. 'ignore' provides a fallback server that trivially 'respond's with output to a client, but ignores all request parameters. -} -- | Discard all responses discard :: (Monad m) => () -> Proxy () a () C m r discard () = forever $ request () -- | Ignore all requests ignore :: (Monad m) => a -> Proxy C () a () m r ignore _ = forever $ respond ()