pipes 2.2.0 → 2.3.0
raw patch · 4 files changed
+845/−14 lines, 4 files
Files
- Control/Pipe.hs +13/−12
- Control/Proxy.hs +331/−0
- Control/Proxy/Tutorial.hs +492/−0
- pipes.cabal +9/−2
Control/Pipe.hs view
@@ -1,6 +1,8 @@ {-| 'Pipe' is a monad transformer that enriches the base monad with the ability to 'await' or 'yield' data to and from other 'Pipe's.++ For an extended tutorial, consult "Control.Pipe.Tutorial". -} module Control.Pipe (@@ -63,12 +65,11 @@ -} -- | The base functor for the 'Pipe' type-data PipeF a b x = Await (a -> x) | Yield (b, x)+data PipeF a b x = Await (a -> x) | Yield b x --- I could use the "DerivingFunctor" extension, but I want to remain portable instance Functor (PipeF a b) where- fmap f (Await a) = Await $ fmap f a- fmap f (Yield y) = Yield $ fmap f y+ fmap f (Await g) = Await (f . g)+ fmap f (Yield b x) = Yield b (f x) {-| The base type for pipes@@ -122,12 +123,12 @@ 'yield' restores control back upstream and binds the result to 'await'. -} yield :: (Monad m) => b -> Pipe a b m ()-yield b = wrap $ Yield (b, return ())+yield b = wrap $ Yield b (return ()) {-| Convert a pure function into a pipe -> pipe = forever $ do+> pipe f = forever $ do > x <- await > yield (f x) -}@@ -202,13 +203,13 @@ let p1' = FreeT $ return x1 runFreeT $ case x1 of Pure r -> return r- Free (Yield y ) -> wrap $ Yield $ fmap (<+< p2) y+ Free (Yield b p1') -> wrap $ Yield b $ p1' <+< p2 Free (Await f1) -> FreeT $ do x2 <- runFreeT p2 runFreeT $ case x2 of- Pure r -> return r- Free (Yield (x, p)) -> f1 x <+< p- Free (Await f2 ) -> wrap $ Await $ fmap (p1' <+<) f2+ Pure r -> return r+ Free (Yield b p2') -> f1 b <+< p2'+ Free (Await f2 ) -> wrap $ Await $ \a -> p1' <+< f2 a -- | Corresponds to ('>>>') from @Control.Category@ (>+>) :: (Monad m) => Pipe a b m r -> Pipe b c m r -> Pipe a c m r@@ -269,5 +270,5 @@ e <- runFreeT p case e of Pure r -> return r- Free (Await f) -> runPipe $ f ()- Free (Yield y) -> runPipe $ snd y+ Free (Await f) -> runPipe $ f ()+ Free (Yield _ p) -> runPipe p
+ Control/Proxy.hs view
@@ -0,0 +1,331 @@+{-| 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 (+ -- * Types+ -- $types+ ProxyF(..),+ Proxy,+ Server,+ Client,+ Session,+ -- * Build Proxies+ -- $build+ request,+ respond,+ -- * Compose Proxies+ -- $compose+ (<-<),+ (>->),+ idT,+ -- * Run Sessions + -- $run+ runSession,+ -- * Utility functions+ -- $utility+ discard,+ ignore,+ foreverK,+ -- * Pipe compatibility+ -- $pipe+ Pipe,+ Producer,+ Consumer,+ Pipeline,+ await,+ yield,+ pipe,+ (<+<),+ (>+>),+ idP,+ runPipe+ ) where++import Control.Monad+import Control.Monad.Trans.Class+import Control.Monad.Trans.Free+import Data.Void++-- Imports for Haddock links+import Control.Category ((<<<), (>>>), id, (.))+import Prelude hiding ((.), id)++{- $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+type Proxy a' a b' b = FreeT (ProxyF a' a b' b)++{-| @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 Void () 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 () Void++{-| A self-contained 'Session', ready to be run by 'runSession'++ 'Session's never 'request' anything or 'respond' to anything. -}+type Session = Proxy Void () () Void++{- $build+ @Proxy@ forms both a monad and a monad transformer. This means you can+ assemble a 'Proxy' using @do@ notation using only 'request', 'respond', and+ 'lift':++> truncate :: Int -> Int -> Proxy Int ByteString Int ByteString IO r+> truncate maxBytes bytes = do+> when (bytes > maxBytes) $ lift $ putStrLn "Input truncated"+> bs <- request (min bytes maxBytes)+> bytes' <- respond bs+> truncate maxBytes bytes'++ You define a 'Proxy' as a function of its initial input (@bytes@ in the+ above example), and subsequent inputs are bound by the 'respond' command.+-}++{-| 'request' input from upstream, passing an argument with the request++ @request a'@ passes @a'@ as a parameter to upstream that upstream can use to+ decide what response to return. 'request' binds the response to its return+ value. -}+request :: (Monad m) => a' -> Proxy a' a b' b m a+request a' = liftF $ Request a' id++{-| 'respond' with an output for downstream and bind downstream's next 'request'++ @respond b@ satisfies a downstream 'request' by supplying the value @b@.+ 'respond' blocks until downstream 'request's a new value and binds the+ argument from the next 'request' as its return value. -}+respond :: (Monad m) => b -> Proxy a' a b' b m b'+respond b = liftF $ Respond b id++{- $compose+ 'Proxy' defines a 'Category', where the objects are the interfaces and the+ morphisms are 'Proxy's parametrized on their initial input.++ ('<-<') is composition and 'idT' is the identity. The identity laws+ guarantee that 'idT' is truly transparent:++> idT <-< p = p+>+> p <-< idT = p++ ... and the associativity law guarantees that 'Proxy' composition does not+ depend on the grouping:++> (p1 <-< p2) <-< p3 = p1 <-< (p2 <-< p3)++ Note that in order to compose 'Proxy's, you must write them as functions of+ their initial argument. All subsequent arguments are bound by the 'respond'+ command. In other words, the actual composable unit is:++> composable :: (Monad m) => b' -> Proxy a' a b' b m r+-}++infixr 9 <-<+infixl 9 >->++{-| Compose two proxies, satisfying all requests from downstream with responses+ from upstream++ Corresponds to ('.')/('<<<') from @Control.Category@ -}+(<-<) :: (Monad m)+ => (c' -> Proxy b' b c' c m r)+ -> (b' -> Proxy a' a b' b m r)+ -> (c' -> Proxy a' a c' c m r)+p1 <-< p2 = \c' -> FreeT $ do+ x1 <- runFreeT $ p1 c'+ runFreeT $ case x1 of+ Pure r -> return r+ Free (Respond c fc') -> wrap $ Respond c (fc' <-< p2)+ Free (Request b' fb ) -> FreeT $ do+ x2 <- runFreeT $ p2 b'+ runFreeT $ case x2 of+ Pure r -> return r+ Free (Respond b fb') -> ((\_ -> fb b) <-< fb') c'+ Free (Request a' fa ) -> do+ let p1' = \_ -> FreeT $ return x1+ wrap $ Request a' $ \a -> (p1' <-< (\_ -> fa a)) c'++{-| Compose two proxies, satisfying all requests from downstream with responses+ from upstream++ Corresponds to ('>>>') from @Control.Category@ -}+(>->) :: (Monad m)+ => (b' -> Proxy a' a b' b m r)+ -> (c' -> Proxy b' b c' c m r)+ -> (c' -> Proxy a' a c' c m r)+(>->) = flip (<-<)++{-| 'idT' acts like a \'T\'ransparent 'Proxy', passing all requests further+ upstream, and passing all responses further downstream.++ Corresponds to 'id' from @Control.Category@ -}+idT :: (Monad m) => a' -> Proxy a' a a' a m r+idT = \a' -> wrap $ Request a' $ \a -> wrap $ Respond a idT+-- i.e. idT = foreverK $ request >=> respond++{- $run+ 'runSession' ensures that the 'Proxy' passed to it does not have any+ open responses or requests. This restriction makes 'runSession' less+ polymorphic than it could be, and I settled on this restriction for four+ reasons:++ * It prevents against accidental data loss.++ * It protects against silent failures++ * It prevents wastefully draining a scarce resource by gratuitously+ driving it to completion++ * It encourages an idiomatic programming style where unfulfilled requests+ or responses are satisfied in a structured way using composition.++ If you believe that loose requests or responses should be discarded or+ ignored, then you can explicitly ignore them by using 'discard' (which+ discards all responses), and 'ignore' (which ignores all requests):++> runSession $ discard <-< p <-< ignore+-}+-- | Run a self-contained 'Session', converting it back to the base monad+runSession :: (Monad m) => (() -> Session m r) -> m r+runSession p = runSession' $ p ()++runSession' p = do+ x <- runFreeT p+ case x of+ Pure r -> return r+ Free (Respond _ fb ) -> runSession' $ fb ()+ Free (Request _ fa') -> runSession' $ fa' ()++{- $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.++ Use 'foreverK' to abstract away the following common pattern:++> p a = do+> ...+> a' <- respond b+> p a'++ Using 'foreverK', you can avoid the manual recursion:++> p = foreverK $ \a -> do+> ...+> respond b+-}++-- | Discard all responses+discard :: (Monad m) => () -> Client () a m r+discard () = forever $ request ()++-- | Ignore all requests+ignore :: (Monad m) => a -> Server a () m r+ignore _ = forever $ respond ()++-- | Compose a \'K\'leisli arrow with itself forever+foreverK :: (Monad m) => (a -> m a) -> (a -> m b)+foreverK k = let r = k >=> r in r+{- foreverK uses 'let' to avoid a space leak.+ See: http://hackage.haskell.org/trac/ghc/ticket/5205 -}++{- $pipe+ The following definitions are drop-in replacements for their 'Pipe'+ equivalents. Consult "Control.Pipe" and "Control.Pipe.Tutorial" for more+ extensive documentation. -}++{-| The type variables of @Pipe a b m r@ signify:++ * @a@ - The type of input received from upstream pipes++ * @b@ - The type of output delivered to downstream pipes++ * @m@ - The base monad++ * @r@ - The type of the return value -}+type Pipe a b = Proxy () a () b++-- | A pipe that produces values+type Producer b = Pipe () b++-- | A pipe that consumes values+type Consumer a = Pipe a Void++-- | A self-contained pipeline that is ready to be run+type Pipeline = Pipe () Void++{-| Wait for input from upstream++ 'await' blocks until input is available -}+await :: (Monad m) => Pipe a b m a+await = request ()++-- | Convert a pure function into a pipe+pipe :: (Monad m) => (a -> b) -> Pipe a b m r+pipe f = forever $ do+ x <- await+ yield (f x)++{-| Deliver output downstream++ 'yield' restores control back downstream and binds the result to 'await'. -}+yield :: (Monad m) => b -> Pipe a b m ()+yield = respond++infixr 9 <+<+infixl 9 >+>++-- | Corresponds to ('<<<')/('.') from @Control.Category@+(<+<) :: (Monad m) => Pipe b c m r -> Pipe a b m r -> Pipe a c m r+p1 <+< p2 = ((\() -> p1) <-< (\() -> p2)) ()++-- | Corresponds to ('>>>') from @Control.Category@+(>+>) :: (Monad m) => Pipe a b m r -> Pipe b c m r -> Pipe a c m r+(>+>) = flip (<+<)++-- | Corresponds to 'id' from @Control.Category@+idP :: (Monad m) => Pipe a a m r+idP = idT ()++-- | Run the 'Pipe' monad transformer, converting it back to the base monad+runPipe :: (Monad m) => Pipeline m r -> m r+runPipe p = do+ x <- runFreeT p+ case x of+ Pure r -> return r+ Free (Request _ f) -> runPipe (f ())+ Free (Respond _ f) -> runPipe (f ())
+ Control/Proxy/Tutorial.hs view
@@ -0,0 +1,492 @@+-- | This module provides the tutorial for "Control.Proxy"++module Control.Proxy.Tutorial (+ -- * Basics+ -- $basics++ -- * Types+ -- $types++ -- * Composition+ -- $composition++ -- * Idioms+ -- $idioms++ -- * The importance of compositionality+ -- $compose++ -- * Mixing monads and composition+ -- $monads++ -- * Pipe Compatibility+ -- $pipes+ ) where++import Control.Monad.Trans.Class+import Control.Proxy++{- $basics+ The 'Proxy' type models composable chains of client-server interactions.++ A 'Proxy' is a monad transformer that extends the base monad with the+ ability to 'request' input from upstream and 'respond' with output to+ downstream.++ For example, consider the following toy remote procedure call+ 'Server':++> import Control.Proxy+> import Control.Monad.Trans+>+> incrementer :: Int -> Server Int Int IO r+> incrementer question = do+> lift $ putStrLn $ "Server received : " ++ show question+> let answer = question + 1+> lift $ putStrLn $ "Server responded: " ++ show answer+> nextQuestion <- respond answer+> incrementer nextQuestion++ We can understand what the 'Server' does just by looking at the type:++> | Question | Answer | Base monad | Return value+> Server Int Int IO r++ Our 'Server' receives questions about 'Int's, and responds with answers that+ are 'Int's. The base monad is 'IO' because our 'Server' 'lift's two+ 'putStrLn' statements to chat out loud. The return value is polymorphic+ because our 'Server' never terminates.++ Note that the base monad doesn't always need to be 'IO'. Unlike typical+ servers, these kinds of 'Server's are pure syntax trees with no side+ effects unless you call 'lift'.++ Now we can write a 'Client' that interacts with our 'Server':++> import Control.Monad+>+> oneTwoThree :: () -> Client Int Int IO ()+> oneTwoThree () = forM_ [1, 2, 3] $ \question -> do+> lift $ putStrLn $ "Client requested: " ++ show question+> answer <- request question+> lift $ putStrLn $ "Client received : " ++ show answer+> lift $ putStrLn "*"++ Again, the type explains what the 'Client' does:++> | Question | Answer | Base monad | Return value+> Client Int | Int | IO | ()++ Our 'Client' asks questions about 'Int's and receives answers that are+ 'Int's. The 'Client' also uses 'IO' as the base monad.++ We can then compose the 'Client' and 'Server' into a 'Session' using the+ ('<-<') operator:++> session :: () -> Session IO ()+> session = oneTwoThree <-< incrementer++ The 'Session' type indicates that we have a self-contained session that we+ can run in the 'IO' monad. We run it using the the 'runSession' function:++>>> runSession session :: IO ()+Client requested: 1+Server received : 1+Server responded: 2+Client received : 2+*+Client requested: 2+Server received : 2+Server responded: 3+Client received : 3+*+Client requested: 3+Server received : 3+Server responded: 4+Client received : 4+*++ Now, let's add an intermediate 'Proxy' between the 'Client' and 'Server'+ that subtly tampers with the stream going through it:++> malicious :: Int -> Proxy Int Int Int Int IO r+> malicious question = do+> question' <- if (question > 2)+> then do+> lift $ putStrLn "MUAHAHAHA!"+> return (question + 1)+> else return question+> answer <- request question'+> nextQuestion <- respond answer+> malicious nextQuestion++ The type tells us what our 'Proxy' does:++> | Upstream (Server) | Downstream (Client) |+> | Question | Answer | Question | Answer | Base monad | Return value+> Proxy Int Int Int Int IO r++ A 'Proxy' bridges two separate interfaces. The first two parameters define+ the upstream interface (i.e. in the 'Server' direction) and the second two+ parameters define the downstream interface (i.e. in the 'Client' direction).++ We can see if our proxy does its job correctly:++>>> runSession $ oneTwoThree <-< malicious <-< incrementer+Client requested: 1+Server received : 1+Server responded: 2+Client received : 2+*+Client requested: 2+Server received : 2+Server responded: 3+Client received : 3+*+Client requested: 3+MUAHAHAHA!+Server received : 4+Server responded: 5+Client received : 5+*++ We can also add more proxies as we see fit:++>>> runSession $ oneTwoThree <-< malicious <-< malicious <-< incrementer +Client requested: 1+Server received : 1+Server responded: 2+Client received : 2+*+Client requested: 2+Server received : 2+Server responded: 3+Client received : 3+*+Client requested: 3+MUAHAHAHA!+MUAHAHAHA!+Server received : 5+Server responded: 6+Client received : 6+*+-}++{- $types+ You probably noticed something odd: ('<-<') seems to be composing values of+ different types. Sometimes it composes a 'Server' or a 'Client' or a+ 'Proxy'. In reality, though, both 'Server' and 'Client' are just type+ synonyms for special cases of 'Proxy':++> type Server arg ret = Proxy Void () arg ret+> type Client arg ret = Proxy arg ret () Void++ A 'Server' is just a 'Proxy' that has no upstream interface, and a 'Client'+ is just a 'Proxy' that has no downstream interface. In fact, 'Session' is+ a 'Proxy', too:++> type Session = Proxy Void () () Void++ A 'Session' is just a 'Proxy' that has neither an upstream interface nor a+ downstream interface.++ The 'Proxy' is the unifying type of the module that all other types derive+ from and ('<-<') always composes two 'Proxy's and returns a new 'Proxy' of+ the correct type.++ You also probably noticed another odd thing: we parametrize every 'Proxy'+ on its initial argument:++> +- Initial Arg+> |+> v+> incrementer :: Int -> Server Int Int IO r+> malicious :: Int -> Proxy Int Int Int Int IO r+> oneTwoThree :: () -> Client Int Int IO ()+>+> session :: () -> Session IO ()++ This input initializes each 'Proxy' and corresponds to the input on the+ downstream interface. I will expand the 'Server' and 'Client' type synonyms+ to show this:++> +- Initial Arg = This -++> | |+> v v+> incrementer :: Int -> Proxy Void () Int Int IO r+> malicious :: Int -> Proxy Int Int Int Int IO r+> oneTwoThree :: () -> Proxy Int Int () Void IO ()+>+> session :: () -> Proxy Void () () Void IO ()++ Composition supplies the first request through this initial parameters+ and all subsequent requests are bound to 'respond' statements.++ This means that the actual types you compose are all of the form:++> proxy :: req_b -> Proxy req_a resp_a req_b resp_b m r+-}++{- $composition+ 'Proxy' composition posseses an identity 'Proxy' that is completely+ transparent to anything upstream or downstream of it:++> idT :: (Monad m) => req -> Proxy req resp req resp m r+> idT question = do+> answer <- request question+> nextQuestion <- respond answer+> idT nextQuestion++ Transparency means that:++> idT <-< p = p+>+> p <-< idT = p++ Also, 'Proxy' composition has the nice property that it behaves exactly the+ same way no matter how you group components:++> (p1 <-< p2) <-< p3 = p1 <-< (p2 <-< p3)++ This means that ('<-<') and 'idT' define a category, and the above equations+ are the category laws. These laws guarantee the following nice+ properties of components:++ * You can reason about each component's behavior independently of other+ components++ * You don't encounter boundary cases between components++ * You don't encounter edge cases at the 'Server' or 'Client' ends++ The semantics of 'Proxy' composition are simple:++ * 'request' blocks until it receives a response from upstream++ * 'respond' blocks until it receives a new request from downstream++ * If any 'Proxy' in the chain terminates, the entire chain terminates+-}++{- $idioms+ We frequently encounter the following recurring pattern when writing+ 'Proxy's:++> someProxy arg = do+> ...+> nextArg <- respond x+> someProxy nexArg++ "Control.Proxy" provides the 'foreverK' utility function which abstracts+ away this manual recursion:++> foreverK f = f >=> foreverK f++ Using 'foreverK', we can simplify the definition of 'incrementer':++> incrementer = foreverK $ \question -> do+> lift $ putStrLn $ "Server received : " ++ show question+> let answer = question + 1+> lift $ putStrLn $ "Server responded: " ++ show answer+> respond answer++ ... which looks exactly like the way you might write server code in another+ programming language.++ We can similarly simplify 'malicious' this way:++> malicious = foreverK $ \question -> do+> question' <- if (question > 2)+> then do+> lift $ putStrLn "MUAHAHAHA!"+> return (question + 1)+> else return question+> answer <- request question'+> respond answer++ ... or 'idT':++> idT = foreverK $ \question -> do+> answer <- request question+> respond answer+>+> -- or: idT = foreverK (request >=> respond)+> -- = request >=> respond >=> request >=> respond >=> ...+-}++{- $compose+ We can mix and match different components to rapidly define emergent+ behaviors from a resuable set of core primitives. For example, we could+ replace our client with a command line prompt where the user requests+ inputs:++> inputPrompt :: (Read a, Show b) => () -> Client a b IO r+> inputPrompt () = forever $ do+> str <- lift $ getLine+> let a = read str+> b <- request a+> lift $ print b+> lift $ putStrLn "*"++>>> runSession $ inputPrompt <-< incrementer+42<Enter>+Server received : 42+Server responded: 43+43+*+666<Enter>+Server received : 666+Server responded: 667+667+*++ Oh no, we lost our useful client diagnostic messages! No worries, we can+ abstract that functionality away into its own component:++> diagnoseClient :: (Show a, Show b) => a -> Proxy a b a b IO r+> diagnoseClient = foreverK $ \a -> do+> lift $ putStrLn $ "Client requested: " ++ show a+> b <- request a+> lift $ putStrLn $ "Client received : " ++ show b+> respond b++>>> runSession $ inputPrompt <-< diagnoseClient <-< incrementer+42<Enter>+Client requested: 42+Server received : 42+Server responded: 43+Client received : 43+43+*+666<Enter>+Client requested: 666+Server received : 666+Server responded: 667+Client received : 667+667+*++ Because of associativity, we can bundle @inputPrompt@ and @diagnoseClient@+ into a single black box and not worry that the abstraction will leak due to+ grouping issues:++> verboseInput :: (Read a, Show b, Show a) => () -> Client a b IO r+> verboseInput = inputPrompt <-< diagnoseClient++>>> runSession $ verboseInput <-< incrementer+<Exactly same behavior>++ Or what if I want to cache the results coming out of @incrementer@? I can+ define a 'Proxy' to cache all requests going through it:++> import qualified Data.Map as M+>+> cache :: (Ord a) => a -> Proxy a b a b IO r+> cache = cache' M.empty+>+> cache' m a =+> case M.lookup a m of+> Nothing -> do+> b <- request a+> a' <- respond b+> cache' (M.insert a b m) a'+> Just b -> do+> lift $ putStrLn "Used cache!"+> a' <- respond b+> cache' m a'++>>> runSession $ verboseInput <-< cache <-< incrementer +42<Enter>+Client requested: 42+Server received : 42+Server responded: 43+Client received : 43+43+*+42<Enter>+Client requested: 42+Used cache!+Client received : 43+43+*++ Note that I don't distinguish between a "reverse proxy" or a "forward proxy"+ since composition doesn't distinguish either. You can attach the @cache@+ 'Proxy' to a 'Client':++> client' = client <-< cache++ ... or to a 'Server':++> server' = cache <-< server++ ... or anywhere in between. It's completely up to you!+-}++{- $monads+ All the previous examples use a single composition chain, but you need not+ restrict yourself to that design pattern. Remember that the result of+ composition is a 'Proxy' itself (parametrized by an input), and 'Proxy's are+ 'Monad's, so you can bind the result of composition directly within another+ @do@ block to generate complex behaviors:++> mixedClient :: () -> Client Int Int IO r+> mixedClient () = do+> oneTwoThree ()+> -- Here we bind composition within a larger do block+> (inputPrompt <-< cache) ()++>>> runSession $ mixedClient <-< incrementer+Client requested: 1+Server received : 1+Server responded: 2+Client received : 2+*+Client requested: 2+Server received : 2+Server responded: 3+Client received : 3+*+Client requested: 3+Server received : 3+Server responded: 4+Client received : 4+*+42<Enter>+Server received : 42+Server responded: 43+43+*+42<Enter>+Used cache!+43+*++ So feel free to use your imagination! Up until the moment you call+ 'runSession', you can freely mix composition or @do@ notation within each+ other.+-}++{- $pipes+ 'Proxy's generalize 'Pipe's by permitting communication upstream.+ Fortunately, though, you don't need to rewrite your code if you have already+ used 'Pipe's. "Control.Proxy" formulates all of the 'Pipe' types and+ primitives in terms of the 'Proxy' type.++ This means that if you wish to upgrade your 'Pipe' code to take advantage of+ upstream communication, you only need to import "Control.Proxy" instead+ of "Control.Pipe" and everything will still work out of the box. Then you+ can selectively upgrade certain components to communicate upstream as+ necessary.++ To understand how 'Pipe's map onto 'Proxy's, just check out the 'Pipe'+ definition in "Control.Proxy":++> type Pipe a b = Proxy () a () b++ In other words, a 'Pipe' is just a 'Proxy' where you never pass any+ informationupstream.++ "Control.Pipe" will not be deprecated, however, and will be preserved for+ users who do not wish to communicate information upstream.+-}
pipes.cabal view
@@ -1,5 +1,5 @@ Name: pipes-Version: 2.2.0+Version: 2.3.0 Cabal-Version: >=1.14.0 Build-Type: Simple License: BSD3@@ -39,8 +39,13 @@ Vertical Concatenation always works the way you expect, picking up where the previous 'Pipe' left off. .+ * /Bidirectionality/: The library now provides a bidirectional 'Pipe' type,+ called a 'Proxy'.+ . Check out "Control.Pipe.Tutorial" for a copious introductory tutorial and- "Control.Pipe" for the actual implementation.+ "Control.Pipe" for the actual implementation. "Control.Proxy.Tutorial"+ introduces bidirectional iteratees that are backwards-compatible with 'Pipe's+ and "Control.Proxy" provides the implementation. Category: Control, Enumerator Tested-With: GHC ==7.4.1 Source-Repository head@@ -58,6 +63,8 @@ Control.Frame, Control.Frame.Tutorial, Control.IMonad.Trans.Free,+ Control.Proxy,+ Control.Proxy.Tutorial, Control.Pipe, Control.Pipe.Tutorial GHC-Options: -O2