pipes 2.3.0 → 2.4.0
raw patch · 25 files changed
+2623/−704 lines, 25 filesdep +freedep −transformers-freedep −void
Dependencies added: free
Dependencies removed: transformers-free, void
Files
- Control/Frame.hs +5/−8
- Control/Frame/Tutorial.hs +3/−3
- Control/MFunctor.hs +14/−0
- Control/Pipe.hs +9/−269
- Control/Pipe/Core.hs +209/−0
- Control/Pipe/Tutorial.hs +72/−35
- Control/Proxy.hs +21/−320
- Control/Proxy/Class.hs +142/−0
- Control/Proxy/Core.hs +233/−0
- Control/Proxy/Pipe.hs +92/−0
- Control/Proxy/Prelude.hs +21/−0
- Control/Proxy/Prelude/Base.hs +329/−0
- Control/Proxy/Prelude/IO.hs +211/−0
- Control/Proxy/Prelude/Kleisli.hs +53/−0
- Control/Proxy/Trans.hs +38/−0
- Control/Proxy/Trans/Either.hs +128/−0
- Control/Proxy/Trans/Identity.hs +70/−0
- Control/Proxy/Trans/Maybe.hs +79/−0
- Control/Proxy/Trans/Reader.hs +105/−0
- Control/Proxy/Trans/State.hs +118/−0
- Control/Proxy/Trans/Tutorial.hs +415/−0
- Control/Proxy/Trans/Writer.hs +111/−0
- Control/Proxy/Tutorial.hs +98/−40
- Data/Closed.hs +8/−0
- pipes.cabal +39/−29
Control/Frame.hs view
@@ -57,8 +57,8 @@ import Control.IMonad.Trans import Control.IMonad.Trans.Free import Control.Monad.Instances ()+import Data.Closed (C) import Data.Maybe-import Data.Void import Prelude hiding ((.), id) -- For documentation@@ -70,13 +70,13 @@ \"@Frame b m (M a) C r@\". For example, given the following type signatures from the tutorial: -> printer :: (Show a) => Pipe b Void IO r+> printer :: (Show a) => Pipe b C IO r > take' :: Int -> Pipe b b IO () > fromList :: (Monad m) => [b] -> Pipe () b m () ... you would replace them with: -> printer :: (Show a) => Frame Void IO (M a) C r+> printer :: (Show a) => Frame C IO (M a) C r > take' :: Int -> Frame a IO (M a) C () > fromList :: (Monad m) => [a] -> Frame a m (M ()) C () > -- To use the finalization example, change fromList's base monad to 'IO'@@ -86,9 +86,6 @@ -- | Index representing an open input end, receiving values of type @a@ data O a --- | Index representing a closed input end-data C- -- | Index representing an open input end, receiving values of type @Maybe a@ type M a = O (Maybe a) @@ -128,7 +125,7 @@ type Frame b m i j r = IFreeT (FrameF (m (), b)) (U m) (r := j) i -- | A self-contained 'Frame' that is ready to be run-type Stack m r = Frame Void m (M ()) C r+type Stack m r = Frame C m (M ()) C r -- $create -- The second step to convert 'Pipe' code to 'Frame' code is to change your@@ -469,7 +466,7 @@ Wrap (Yield _ p') -> runFrame p' Wrap (Await f ) -> runFrame (f $ Just ()) -runFrame' :: (Monad m) => Frame Void m C C r -> m r+runFrame' :: (Monad m) => Frame C m C C r -> m r runFrame' p = do x <- unU $ runIFreeT p case x of
Control/Frame/Tutorial.hs view
@@ -189,7 +189,7 @@ terminates: > -- This type-checks because foreverR is polymorphic in the final index-> printer :: (Show b) => Frame Void IO (M b) C r+> printer :: (Show b) => Frame C IO (M b) C r > printer = foreverR $ do > a <- await > liftU $ print a@@ -230,7 +230,7 @@ The 'Frame' equivalent to 'Pipeline' is a 'Stack' (mnemonic: call stack; also the name 'Frame' refers to a call stack frame): -> type Stack m r = Frame Void m (M ()) C r+> type Stack m r = Frame C m (M ()) C r Similarly, you use 'runFrame' instead of 'runPipe' to convert the 'Frame' back to the base monad:@@ -253,7 +253,7 @@ before it may be used. 'runFrame' has the exact same restriction: > runFrame :: Monad m => Stack m r -> m r-> runFrame ~ Monad m => Frame Void m (M ()) C r -> m r+> runFrame ~ Monad m => Frame C m (M ()) C r -> m r Composition specifically requires the user to define when to finalize upstream and does not assume this occurs at the end of the 'Frame'. This
+ Control/MFunctor.hs view
@@ -0,0 +1,14 @@+-- | This module temporarily holds this class until it can find a better home.++{-# LANGUAGE Rank2Types #-}++module Control.MFunctor (+ -- * Monads over functors+ MFunctor(..)+ ) where++-- | A functor in the category of monads+class MFunctor t where+ {-| Lift a monad morphism from @m@ to @n@ into a monad morphism from+ @(t m)@ to @(t n)@ -}+ mapT :: (Monad m, Monad n) => (forall a . m a -> n a) -> t m b -> t n b
Control/Pipe.hs view
@@ -1,274 +1,14 @@-{-|- '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".--}+-- | Top-level import for the "Control.Pipe" hierarchy module Control.Pipe (- -- * Introduction- -- $summary-- -- * Types- -- $types- PipeF(..),- Pipe,- Producer,- Consumer,- Pipeline,- -- * Create Pipes- -- $create- await,- yield,- pipe,- -- * Compose Pipes- -- $category- (<+<),- (>+>),- idP,- PipeC(..),- -- * Run Pipes- -- $runpipe- runPipe+ -- * Modules+ -- $modules+ module Control.Pipe.Core ) where -import Control.Applicative-import Control.Category-import Control.Monad (forever)-import Control.Monad.Trans.Class (lift)-import Control.Monad.Trans.Free-import Data.Void (Void)-import Prelude hiding ((.), id)--{- $summary- I completely expose the 'Pipe' data type and internals in order to encourage- people to write their own 'Pipe' functions. This does not compromise the- correctness or safety of the library at all and you can feel free to use the- constructors directly without violating any laws or invariants.-- I promote using the 'Monad' and 'Category' instances to build and compose- pipes, but this does not mean that they are the only option. In fact, any- combinator provided by other iteratee libraries can be recreated for pipes,- too. However, this core library does not provide many of the functions- found in other libraries in order to encourage people to find principled and- theoretically grounded solutions rather than devise ad-hoc solutions- characteristic of other iteratee implementations.--}--{- $types- The 'Pipe' type is strongly inspired by Mario Blazevic's @Coroutine@ type in- his concurrency article from Issue 19 of The Monad Reader and is formulated- in the exact same way.-- His @Coroutine@ type is actually a free monad transformer (i.e. 'FreeT')- and his @InOrOut@ functor corresponds to 'PipeF'.--}---- | The base functor for the 'Pipe' type-data PipeF a b x = Await (a -> x) | Yield b x--instance Functor (PipeF a b) where- fmap f (Await g) = Await (f . g)- fmap f (Yield b x) = Yield b (f x)--{-|- The base type for pipes-- * @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 = FreeT (PipeF a b)---- | A pipe that produces values-type Producer b = Pipe () b---- | A pipe that consumes values-type Consumer b = Pipe b Void---- | A self-contained pipeline that is ready to be run-type Pipeline = Pipe () Void--{- $create- 'yield' and 'await' are the only two primitives you need to create pipes.- Since @Pipe a b m@ is a monad, you can assemble 'yield' and 'await'- statements using ordinary @do@ notation. Since @Pipe a b@ is also a monad- transformer, you can use 'lift' to invoke the base monad. For example, you- could write a pipe stage that requests permission before forwarding any- output:--> check :: (Show a) => Pipe a a IO r-> check = forever $ do-> x <- await-> lift $ putStrLn $ "Can '" ++ (show x) ++ "' pass?"-> ok <- read <$> lift getLine-> when ok (yield x)--}--{-|- Wait for input from upstream.-- 'await' blocks until input is available from upstream.--}-await :: (Monad m) => Pipe a b m a-await = wrap $ Await return--{-|- Deliver output downstream.-- '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 ())--{-|- Convert a pure function into a pipe--> pipe f = forever $ do-> x <- await-> yield (f x)--}-pipe :: (Monad m) => (a -> b) -> Pipe a b m r-pipe f = forever $ await >>= yield . f--{- $category- 'Pipe's form a 'Category', meaning that you can compose 'Pipe's and also- define an identity 'Pipe'.-- 'Pipe' composition binds the output of the upstream 'Pipe' to the input of- the downstream 'Pipe'. Like Haskell functions, 'Pipe's are lazy, meaning- that upstream 'Pipe's are only evaluated as far as necessary to generate- enough input for downstream 'Pipe's. If any 'Pipe' terminates, it also- terminates every 'Pipe' composed with it.-- If you want to define a proper 'Category' instance you have to wrap the- 'Pipe' type using the newtype 'PipeC' in order to rearrange the type- variables.-- This means that if you want to compose pipes using ('.') from the 'Category'- type class, you end up with a newtype mess:--> unPipeC (PipeC p1 . PipeC p2)-- You can avoid this by using convenient operators that do this newtype- wrapping and unwrapping for you:--> p1 <+< p2 = unPipeC $ PipeC p1 . PipeC p2->-> idP = unPipeC id-- The 'Category' instance obeys the 'Category' laws. In other words:-- * Composition is truly associative. The result of composition produces the- exact same composite 'Pipe' regardless of how you group composition, so it- is perfectly safe to omit the parentheses altogether:--> (p1 <+< p2) <+< p3 = p1 <+< (p2 <+< p3) = p1 <+< p2 <+< p3-- * 'idP' is a true identity pipe. Composing a pipe with 'idP' returns the- exact same original pipe:--> p <+< idP = p-> idP <+< p = p-- The 'Category' laws are \"correct by construction\", meaning that you cannot- break them despite the library's internals being fully exposed. The above- equalities are true using the strongest denotational semantics possible in- Haskell, namely that both sides of the equals sign correspond to the exact- same value in Haskell, constructor-for-constructor, value-for-value. You- cannot create a function that can distinguish the results.-- Actually, all other class instances in this library provide the same strong- guarantees for their corresponding laws. I only emphasize the guarantee for- the 'Category' instance because it is one of the most distinguishing- features of this library, making it far more extensible than other- implementations.--}---- | 'Pipe's form a 'Category' instance when you rearrange the type variables-newtype PipeC m r a b = PipeC { unPipeC :: Pipe a b m r}--instance (Monad m) => Category (PipeC m r) where- id = PipeC idP- PipeC p1 . PipeC p2 = PipeC $ p1 <+< p2---- | Corresponds to ('<<<')/('.') from @Control.Category@-(<+<) :: (Monad m) => Pipe b c m r -> Pipe a b m r -> Pipe a c m r-p1 <+< p2 = FreeT $ do- x1 <- runFreeT p1- let p1' = FreeT $ return x1- runFreeT $ case x1 of- Pure r -> return r- 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 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-(>+>) = flip (<+<)--{- These associativities might help performance since pipe evaluation is- downstream-biased. I set them to the same priority as (.). -}-infixr 9 <+<-infixl 9 >+>---- | Corresponds to 'id' from @Control.Category@-idP :: (Monad m) => Pipe a a m r-idP = pipe id--{- $runpipe- Note that you can also unwrap a 'Pipe' a single step at a time using- 'runFreeT' (since 'Pipe' is just a type synonym for a free monad- transformer). This will take you to the next /external/ 'await' or 'yield'- statement.-- This means that a closed 'Pipeline' will unwrap to a single step, in which- case you would have been better served by 'runPipe'. This directly follows- from the 'Category' laws, which guarantee that you cannot resolve a- composite pipe into its component pipes. When you compose two pipes, the- internal await and yield statements fuse and completely disappear.-- 'runFreeT' is ideal for more advanced users who wish to write their own- 'Pipe' functions while waiting for me to find more elegant solutions.--}-{-|- Run the 'Pipe' monad transformer, converting it back into the base monad.-- 'runPipe' imposes two conditions:-- * The pipe's input, if any, is trivially satisfiable (i.e. @()@)-- * The pipe does not 'yield' any output-- The latter restriction makes 'runPipe' less polymorphic than it could be,- and I settled on the restriction for three reasons:-- * It prevents against accidental data loss.-- * It prevents wastefully draining a scarce resource by gratuitously- demanding values from it.-- * It encourages an idiomatic pipe programming style where input is consumed- in a structured way using a 'Consumer'.-- If you believe that discarding output is the appropriate behavior, you can- specify this by explicitly feeding your output to a pipe that gratuitously- discards it:+import Control.Pipe.Core -> runPipe $ forever await <+< p--}-runPipe :: (Monad m) => Pipeline m r -> m r-runPipe p = do- e <- runFreeT p- case e of- Pure r -> return r- Free (Await f) -> runPipe $ f ()- Free (Yield _ p) -> runPipe p+{- $modules+ "Control.Pipe.Core" provides the core type and primitives.+ + "Control.Pipe.Tutorial" provides an extended tutorial. -}
+ Control/Pipe/Core.hs view
@@ -0,0 +1,209 @@+{-| The 'Pipe' type is a monad transformer that enriches the base monad with the+ ability to 'await' or 'yield' data to and from other 'Pipe's. -}++module Control.Pipe.Core (+ -- * Types+ -- $types+ PipeF(..),+ Pipe,+ C,+ Producer,+ Consumer,+ Pipeline,+ -- * Create Pipes+ -- $create+ await,+ yield,+ pipe,+ -- * Compose Pipes+ -- $category+ (<+<),+ (>+>),+ idP,+ PipeC(..),+ -- * Run Pipes+ -- $runpipe+ runPipe+ ) where++import Control.Applicative (Applicative(pure, (<*>)))+import Control.Category (Category((.), id), (<<<), (>>>))+import Control.Monad (forever)+import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.Free (+ FreeF(Free, Pure), FreeT(FreeT, runFreeT), wrap)+import Data.Closed (C)+import Prelude hiding ((.), id)++{- $types+ The 'Pipe' type is strongly inspired by Mario Blazevic's @Coroutine@ type in+ his concurrency article from Issue 19 of The Monad Reader and is formulated+ in the exact same way.++ His @Coroutine@ type is actually a free monad transformer (i.e. 'FreeT')+ and his @InOrOut@ functor corresponds to 'PipeF'.+-}++-- | The base functor for the 'Pipe' type+data PipeF a b x = Await (a -> x) | Yield b x++instance Functor (PipeF a b) where+ fmap f (Await g) = Await (f . g)+ fmap f (Yield b x) = Yield b (f x)++{-|+ The base type for pipes++ * @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 = FreeT (PipeF a b)++-- | A pipe that produces values+type Producer b = Pipe () b++-- | A pipe that consumes values+type Consumer b = Pipe b C++-- | A self-contained pipeline that is ready to be run+type Pipeline = Pipe () C++{- $create+ 'yield' and 'await' are the only two primitives you need to create pipes.+ Since @Pipe a b m@ is a monad, you can assemble 'yield' and 'await'+ statements using ordinary @do@ notation. Since @Pipe a b@ is also a monad+ transformer, you can use 'lift' to invoke the base monad. For example, you+ could write a pipe stage that requests permission before forwarding any+ output:++> check :: (Show a) => Pipe a a IO r+> check = forever $ do+> x <- await+> lift $ putStrLn $ "Can '" ++ (show x) ++ "' pass?"+> ok <- read <$> lift getLine+> when ok (yield x)+-}++{-|+ Wait for input from upstream.++ 'await' blocks until input is available from upstream.+-}+await :: (Monad m) => Pipe a b m a+await = wrap $ Await return++{-|+ Deliver output downstream.++ '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 ())++{-|+ Convert a pure function into a pipe++> pipe f = forever $ do+> x <- await+> yield (f x)+-}+pipe :: (Monad m) => (a -> b) -> Pipe a b m r+pipe f = forever $ await >>= yield . f++{- $category+ 'Pipe's form a 'Category', meaning that you can compose 'Pipe's using+ ('<+<') and also define an identity 'Pipe': 'idP'. These satisfy the+ category laws:++> idP <+< p = p+>+> p <+< idP = p+>+> (p1 <+< p2) <+< p3 = p1 <+< (p2 <+< p3)++ 'Pipe' composition binds the output of the upstream 'Pipe' to the input of+ the downstream 'Pipe'. Like Haskell functions, 'Pipe's are lazy, meaning+ that upstream 'Pipe's are only evaluated as far as necessary to generate+ enough input for downstream 'Pipe's. If any 'Pipe' terminates, it also+ terminates every 'Pipe' composed with it.+-}++-- | 'Pipe's form a 'Category' instance when you rearrange the type variables+newtype PipeC m r a b = PipeC { unPipeC :: Pipe a b m r}++instance (Monad m) => Category (PipeC m r) where+ id = PipeC idP+ PipeC p1 . PipeC p2 = PipeC $ p1 <+< p2++-- | Corresponds to ('<<<')/('.') from @Control.Category@+(<+<) :: (Monad m) => Pipe b c m r -> Pipe a b m r -> Pipe a c m r+p1 <+< p2 = FreeT $ do+ x1 <- runFreeT p1+ let p1' = FreeT $ return x1+ runFreeT $ case x1 of+ Pure r -> return r+ 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 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+(>+>) = flip (<+<)++{- These associativities might help performance since pipe evaluation is+ downstream-biased. I set them to the same priority as (.). -}+infixr 9 <+<+infixl 9 >+>++-- | Corresponds to 'id' from @Control.Category@+idP :: (Monad m) => Pipe a a m r+idP = pipe id++{- $runpipe+ Note that you can also unwrap a 'Pipe' a single step at a time using+ 'runFreeT' (since 'Pipe' is just a type synonym for a free monad+ transformer). This will take you to the next /external/ 'await' or 'yield'+ statement. This means that a closed 'Pipeline' will unwrap to a single+ step, in which case you would have been better served by 'runPipe'.+-}+{-|+ Run the 'Pipe' monad transformer, converting it back into the base monad.++ 'runPipe' imposes two conditions:++ * The pipe's input, if any, is trivially satisfiable (i.e. @()@)++ * The pipe does not 'yield' any output++ The latter restriction makes 'runPipe' less polymorphic than it could be,+ and I settled on the restriction for three 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++ If you believe that discarding output is the appropriate behavior, you can+ specify this by explicitly feeding your output to a pipe that gratuitously+ discards it:++> runPipe $ forever await <+< p+-}+runPipe :: (Monad m) => Pipeline m r -> m r+runPipe p = do+ e <- runFreeT p+ case e of+ Pure r -> return r+ Free (Await f) -> runPipe $ f ()+ Free (Yield _ p) -> runPipe p
Control/Pipe/Tutorial.hs view
@@ -21,23 +21,26 @@ -- * Termination -- $terminate + -- * Folds+ -- $folds+ -- * Resource Management -- $resource - -- *Frames- -- $frames+ -- * Bidirectional Pipes+ -- $bidirectional ) where -- For documentation import Control.Category import Control.Frame hiding (await, yield) import Control.Monad.Trans.Class+import Control.Monad.Trans.Free import Control.Pipe-import Data.Void {- $type- This library represents streaming computations using a single data type:- 'Pipe'.+ This library represents unidirectional streaming computations using the+ 'Pipe' type. 'Pipe' is a monad transformer that extends the base monad with the ability to 'await' input from or 'yield' output to other 'Pipe's. 'Pipe's resemble@@ -106,15 +109,15 @@ > lift $ print x Here, @printer@ is polymorphic in its output. We could type-restrict it to- guarantee it will never 'yield' by setting the output to 'Void', from- @Data.Void@:+ guarantee it will never 'yield' by setting the output to 'C', an unhabited+ type that \'@C@\'loses the output end: -> printer :: (Show b) => Pipe b Void IO r+> printer :: (Show b) => Pipe b C IO r A 'Pipe' that never 'yield's can be the final stage in a 'Pipeline'. Again, I provide a type synonym for this common case: -> type Consumer b m r = Pipe b Void m r+> type Consumer b m r = Pipe b C m r So we could instead write @printer@'s type as: @@ -134,7 +137,7 @@ For example, you can compose the above 'Pipe's with: -> pipeline :: Pipe () Void IO ()+> pipeline :: Pipe () C IO () > pipeline = unPipeC $ PipeC printer . PipeC (take' 3) . PipeC (fromList [1..]) The compiler deduces that the final 'Pipe' must be blocked at both ends,@@ -142,7 +145,7 @@ output. This represents a self-contained 'Pipeline' and I provide a type synonym for this common case: -> type Pipeline m r = Pipe () Void m r+> type Pipeline m r = Pipe () C m r Also, I provide '<+<' as a convenience operator for composing 'Pipe's without the burden of wrapping and unwrapping newtypes:@@ -181,27 +184,21 @@ * 'Pipe's are lazy, so execution begins at the most downstream 'Pipe' (@printer@ in our example). - * Upstream 'Pipe's only run if input is requested from them and they only- run as long as necessary to 'yield' back a value.-- * If a 'Pipe' terminates, it terminates every other 'Pipe' composed with it.-- Another way to think of this is like a stack where each 'Pipe' is a frame on- that stack:+ * When a 'Pipe' 'await's, it blocks until it receives input from the next+ 'Pipe' upstream - * If a 'Pipe' 'await's input, it blocks and pushes the next 'Pipe' upstream- onto the stack until that 'Pipe' 'yield's back a value.+ * When a 'Pipe' 'yield's, it blocks until it receives a new 'await' request+ from downstream. - * If a 'Pipe' 'yield's output, it pops itself off the stack and restores- control to the original downstream 'Pipe' that was 'await'ing its input.- This binds its result to the return value of the pending 'await' command.+ * If a 'Pipe' terminates, it terminates every other 'Pipe' composed with it. All of these flow control rules uniquely follow from the 'Category' laws. It might surprise you that termination brings down the entire 'Pipeline' until you realize that: - * Downstream 'Pipe's depending on the terminated 'Pipe' cannot proceed+ * Downstream 'Pipe's depending on the result from the terminated 'Pipe'+ cannot proceed * Upstream 'Pipe's won't be further evaluated because the terminated 'Pipe' will not request any further input from them@@ -408,10 +405,6 @@ directly to the desired 'Pipe' and breaks when you introduce intermediate stages. - This was not an intentional design choice, but rather a direct consequence- of enforcing the 'Category' laws when I was implementing 'Pipe''s 'Category'- instance. Satisfying the 'Category' laws forces code to be compositional.- Note that a terminated 'Pipe' only brings down 'Pipe's composed with it. To illustrate this, let's use the following example: @@ -437,9 +430,39 @@ are 'await' and 'yield'. -} +{- $folds+ While we cannot intercept termination, we can still fold our input. We can+ embed 'WriterT' in our base monad, since 'Pipe' is a monad transformer, and+ store the result in the monoid:++> toList :: Consumer a (WriterT [a] m) r+> toList = forever $ do+> a <- await+> lift $ tell [a]++>>> execWriterT $ runPipe $ toList <+< fromList [1..4]+[1,2,3,4]++ But what if other pipes have a base monad that is not compatible, such as:++> prompt3 :: Producer Int IO a+> prompt3 = take' 3 <+< prompt++ That's okay, because we can transparently 'lift' any Pipe's base monad,+ using 'hoistFreeT' from @Control.Monad.Trans.Free@ in the @free@ package:++>>> execWriterT $ runPipe $ toList <+< hoistFreeT lift prompt3+3<Enter>+4<Enter>+6<Enter>+[3,4,6]++-}+ {- $resource- Here's another problem with 'Pipe' composition: resource finalization.- Let's say we have the file \"@test.txt@\" with the following contents:+ Pipes handle streaming computations well, but do not handle resource+ management well. To see why, let's say we have the file \"@test.txt@\"+ with the following contents: > Line 1 > Line 2@@ -493,11 +516,25 @@ too lazy to properly close our file! \"@take' 2@\" terminated before @read'@, preventing @read'@ from properly closing \"test.txt\". This is why 'Pipe' composition fails to guarantee deterministic finalization.++ The "Control.Frame" module of this library provides a temporary solution to+ this problem, but in the longer run there will be a more elegant solution+ built on top of "Control.Proxy". -} -{- $frames- So with 'Pipe's, we can neither write folds, nor can we finalize resources- deterministically. Fortunately, there is a solution: 'Frame's. Check out- "Control.Frame.Tutorial" for an introduction to a type that enriches 'Pipe's- with the ability to fold and finalize resources correctly.+{- $bidirectional+ The 'Pipe' type suffers from one restriction: it only handles a+ unidirectional flow of information. If you want a bidirectional 'Pipe'+ type, then use the 'Proxy' type from "Control.Proxy", which generalizes the+ 'Pipe' type to bidirectional flow.++ More importantly, the 'Proxy' type is a strict superset of the 'Pipe' type,+ so all 'Pipe' utilities and extensions are actually written as 'Proxy'+ utilities and extensions, in order to avoid code duplication.++ So if you want to use these extensions, import "Control.Proxy" instead,+ which exports a backwards compatible 'Pipe' implementation along with all+ utilities and extensions. The 'Pipe' implementation in "Control.Pipe.Core"+ exists purely as a reference implementation for people who wish to study the+ simpler 'Pipe' type when building their own iteratee libraries. -}
Control/Proxy.hs view
@@ -1,331 +1,32 @@-{-| A 'Proxy' 'request's input from upstream and 'respond's with output to- downstream.-- For an extended tutorial, consult "Control.Proxy.Tutorial". -}+-- | Default imports for the "Control.Proxy" hierarchy 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+ -- * Modules+ -- $modules+ module Control.Proxy.Class,+ module Control.Proxy.Core,+ module Control.Proxy.Pipe,+ module Control.Proxy.Trans,+ module Control.Proxy.Prelude ) 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' ()+import Control.Proxy.Class+import Control.Proxy.Core+import Control.Proxy.Pipe+import Control.Proxy.Trans+import Control.Proxy.Prelude -{- $utility- 'discard' provides a fallback 'Client' that gratuitously 'request's input- from a 'Server', but discards all responses.+{- $modules+ "Control.Proxy.Core" provides the core 'Proxy' type. - 'ignore' provides a fallback 'Server' that trivially 'respond's with output- to a 'Client', but ignores all request parameters.+ "Control.Proxy.Class" provides the abstract interface to 'Proxy' operations. - Use 'foreverK' to abstract away the following common pattern:+ "Control.Proxy.Trans" provides proxy transformers. -> p a = do-> ...-> a' <- respond b-> p a'+ "Control.Proxy.Pipe" provides a backwards-compatible re-implementation of+ 'Pipe's. - Using 'foreverK', you can avoid the manual recursion:+ "Control.Proxy.Prelude" provides a standard library of proxies. -> p = foreverK $ \a -> do-> ...-> respond b+ Consult "Control.Proxy.Tutorial" for an extended tutorial. -}---- | 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/Class.hs view
@@ -0,0 +1,142 @@+{-| This module provides an abstract interface to 'Proxy'-like behavior, so that+ multiple proxy implementations can share the same library of utility+ proxies. -}++module Control.Proxy.Class (+ -- * Proxy composition+ Channel(..),+ -- * Proxy request and respond+ Interact(..),+ ) where++{- * I use educated guesses about which associativy is optimal for each operator+ * Keep precedence lower than function composition, which is 9 at the time of+ of this comment -}+infixr 7 <-<+infixl 7 >->+infixr 8 /</+infixl 8 \>\+infixl 8 \<\+infixr 8 />/++{-| The 'Channel' class defines an interface to a bidirectional flow of+ information.++ Laws:++ * ('>->') and 'idT' form a category:++> idT >-> f = f+>+> f >-> idT = f+>+> (f >-> g) >-> h = f >-> (g >-> h)++ Minimal complete definition:++ * 'idT'++ * ('>->') or ('<-<').+-}+class Channel p where+ {-| 'idT' acts like a \'T\'ransparent proxy, passing all requests further+ upstream, and passing all responses further downstream. -}+ idT :: (Monad m) => a' -> p a' a a' a m r++ {-| Compose two proxies, satisfying all requests from downstream with+ responses from upstream. -}+ (>->) :: (Monad m)+ => (b' -> p a' a b' b m r)+ -> (c' -> p b' b c' c m r)+ -> (c' -> p a' a c' c m r)+ p1 >-> p2 = p2 <-< p1++ {-| Compose two proxies, satisfying all requests from downstream with+ responses from upstream. -}+ (<-<) :: (Monad m)+ => (c' -> p b' b c' c m r)+ -> (b' -> p a' a b' b m r)+ -> (c' -> p a' a c' c m r)+ p1 <-< p2 = p2 >-> p1++{-| The 'Interact' class defines the ability to:++ * Request input using the 'request' command++ * Replace existing 'request' commands using ('\>\')++ * Respond with output using the 'respond' command++ * Replace existing 'respond' commands using ('/>/')+ + Laws:++ * ('\>\') and 'request' form a category:++> request \>\ f = f+>+> f \>\ request = f+>+> (f \>\ g) \>\ h = f \>\ (g \>\ h)++ * ('/>/') and 'respond' form a category:++> respond />/ f = f+>+> f />/ respond = f+>+> (f />/ g) />/ h = f />/ (g />/ h)++ Minimal complete definition:++ * 'request',++ * ('\>\') or ('/</'),++ * 'respond', and++ * ('/>/') or ('\<\').+-}+class Interact p where+ {-| 'request' input from upstream, passing an argument with the request++ @request a'@ passes @a'@ as a parameter to upstream that upstream may+ use to decide what response to return. 'request' binds the upstream's+ response to its own return value. -}+ request :: (Monad m) => a' -> p a' a x' x m a++ -- | @f \\>\\ g@ replaces all 'request's in 'g' with 'f'.+ (\>\) :: (Monad m)+ => (b' -> p a' a x' x m b)+ -> (c' -> p b' b x' x m c)+ -> (c' -> p a' a x' x m c)+ p1 \>\ p2 = p2 /</ p1++ -- | @f \/<\/ g@ replaces all 'request's in 'f' with 'g'.+ (/</) :: (Monad m)+ => (c' -> p b' b x' x m c)+ -> (b' -> p a' a x' x m b)+ -> (c' -> p a' a x' x m c)+ p1 /</ p2 = p2 \>\ p1++ {-| '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) => a -> p x' x a' a m a'++ -- | @f \/>\/ g@ replaces all 'respond's in 'f' with 'g'.+ (/>/) :: (Monad m)+ => (a -> p x' x b' b m a')+ -> (b -> p x' x c' c m b')+ -> (a -> p x' x c' c m a')+ p1 />/ p2 = p2 \<\ p1++ -- | @f \\<\\ g@ replaces all 'respond's in 'g' with 'f'.+ (\<\) :: (Monad m)+ => (b -> p x' x c' c m b')+ -> (a -> p x' x b' b m a')+ -> (a -> p x' x c' c m a')+ p1 \<\ p2 = p2 />/ p1
+ Control/Proxy/Core.hs view
@@ -0,0 +1,233 @@+{-| 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, (/</), respond, (/>/)) )+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) <-<? (unProxy . p2))++idT' :: (Monad m) => a' -> FreeT (ProxyF a' a a' a) m r+idT' a' = wrap $ Request a' $ \a -> wrap $ Respond a idT'++(<-<?) :: (Monad m)+ => (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 <-<? 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'++instance Interact Proxy where+ request a' = Proxy $ liftF $ Request a' id+ p1 /</ p2 = (Proxy .) $ (unProxy . p1) /</? (unProxy . p2)+ respond a = Proxy $ liftF $ Respond a id+ p1 />/ p2 = (Proxy .) $ (unProxy . p1) />/? (unProxy . p2)++(/</?)+ :: (Monad m)+ => (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 /</? f2 = \a' -> FreeT $ do+ x1 <- runFreeT $ f1 a'+ runFreeT $ case x1 of+ Pure a -> return a+ Free (Respond x fx') -> wrap $ Respond x $ fx' /</? f2+ Free (Request b' fb ) -> (f2 >=> (fb /</? f2)) b'++(/>/?)+ :: (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 ()
+ Control/Proxy/Pipe.hs view
@@ -0,0 +1,92 @@+{-| This module provides an API compatible with "Control.Pipe"++ Consult "Control.Pipe.Core" for more extensive documentation and+ "Control.Pipe.Tutorial" for an extended tutorial. -}++module Control.Proxy.Pipe (+ -- * Types+ Pipe,+ Producer,+ Consumer,+ Pipeline,+ -- * Create Pipes+ await,+ yield,+ pipe,+ -- * Compose Pipes+ (<+<),+ (>+>),+ idP,+ -- * Run Pipes+ runPipe+ ) where++import Control.Monad (forever)+import Control.Monad.Trans.Free+import Control.Proxy.Core+import Control.Proxy.Class+import Data.Closed (C)++{-| 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 C++-- | A self-contained pipeline that is ready to be run+type Pipeline = Pipe () C++{-| 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 = runPipe' . unProxy++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/Prelude.hs view
@@ -0,0 +1,21 @@+-- | Entry point for the Control.Proxy.Prelude hierarchy++module Control.Proxy.Prelude (+ -- $modules+ module Control.Proxy.Prelude.Base,+ module Control.Proxy.Prelude.IO,+ module Control.Proxy.Prelude.Kleisli+ ) where++import Control.Proxy.Prelude.Base+import Control.Proxy.Prelude.IO+import Control.Proxy.Prelude.Kleisli++{- $modules+ "Control.Proxy.Prelude.Base" provides pure utility proxies.++ "Control.Proxy.Prelude.IO" provides proxies for simple 'IO'.++ "Control.Proxy.Prelude.Kleisli" provides convenience functions for working+ with Kleisli arrows.+-}
+ Control/Proxy/Prelude/Base.hs view
@@ -0,0 +1,329 @@+-- | 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)
+ Control/Proxy/Prelude/IO.hs view
@@ -0,0 +1,211 @@+{-| 'String'-based 'IO' operations.++ Note that 'String's are very inefficient, and I will release future separate+ packages with 'ByteString' and 'Text' operations. I only provide these to+ allow users to test simple I/O without requiring additional library+ dependencies.+-}++module Control.Proxy.Prelude.IO (+ -- * Standard I/O+ -- ** Input+ getLineS,+ getLineC,+ readLnS,+ readLnC,+ -- ** Output+ printB,+ printD,+ printU,+ putStrLnB,+ putStrLnD,+ putStrLnU,+ -- ** Interaction+ promptS,+ promptC,+ -- * Handle I/O+ -- ** Input+ hGetLineD,+ hGetLineU,+ -- ** Output+ hPrintB,+ hPrintD,+ hPrintU,+ hPutStrLnB,+ hPutStrLnD,+ hPutStrLnU+ ) where++import Control.Monad (forever)+import Control.Monad.Trans.Class (lift)+import Control.Proxy.Prelude.Kleisli (foreverK)+import Control.Proxy.Core (Proxy, Client, Server)+import Control.Proxy.Class (request, respond)+import System.IO (Handle, hGetLine, hPutStr, hPutStrLn, hPrint, stdin, stdout)++-- | Get input from 'stdin' one line at a time and send \'@D@\'ownstream+getLineS :: () -> Server () String IO r+getLineS () = forever $ do+ str <- lift getLine+ respond str++-- | Get input from 'stdin' one line at a time and send \'@U@\'pstream+getLineC :: () -> Client String () IO r+getLineC () = forever $ do+ str <- lift getLine+ request str++-- | 'read' input from 'stdin' one line at a time and send \'@D@\'ownstream+readLnS :: (Read a) => () -> Server () a IO r+readLnS () = forever $ do+ a <- lift readLn+ respond a++-- | 'read' input from 'stdin' one line at a time and send \'@U@\'pstream+readLnC :: (Read a) => () -> Client a () IO r+readLnC () = forever $ do+ a <- lift readLn+ request a++{-| 'print's all values flowing through it to 'stdout'++ Prefixes upstream values with \"@U: @\" and downstream values with \"@D: @\"+-}+printB :: (Show a, Show a') => a' -> Proxy a' a a' a IO r+printB = foreverK $ \a' -> do+ lift $ do+ putStr "U: "+ print a'+ a <- request a'+ lift $ do+ putStr "D: "+ print a+ respond a++-- | 'print's all values flowing \'@D@\'ownstream to 'stdout'+printD :: (Show a) => x -> Proxy x a x a IO r+printD = foreverK $ \x -> do+ a <- request x+ lift $ print a+ respond a++-- | 'print's all values flowing \'@U@\'pstream to 'stdout'+printU :: (Show a') => a' -> Proxy a' x a' x IO r+printU = foreverK $ \a' -> do+ lift $ print a'+ x <- request a'+ respond x++{-| 'putStrLn's all values flowing through it to 'stdout'++ Prefixes upstream values with \"@U: @\" and downstream values with \"@D: @\"+-}+putStrLnB :: String -> Proxy String String String String IO r+putStrLnB = foreverK $ \a' -> do+ lift $ do+ putStr "U: "+ putStrLn a'+ a <- request a'+ lift $ do+ putStr "D: "+ putStrLn a+ respond a++-- | 'putStrLn's all values flowing \'@D@\'ownstream to 'stdout'+putStrLnD :: x -> Proxy x String x String IO r+putStrLnD = foreverK $ \x -> do+ a <- request x+ lift $ putStrLn a+ respond a++-- | 'putStrLn's all values flowing \'@U@\'pstream to 'stdout'+putStrLnU :: String -> Proxy String x String x IO r+putStrLnU = foreverK $ \a' -> do+ lift $ putStrLn a'+ x <- request a'+ respond x++-- | Convert 'stdin'/'stdout' into a line-based 'Server'+promptS :: String -> Server String String IO r+promptS = foreverK $ \send -> do+ recv <- lift $ do+ putStrLn send+ getLine+ respond recv++-- | Convert 'stdin'/'stdout' into a line-based 'Client'+promptC :: () -> Client String String IO r+promptC () = forever $ do+ send <- lift getLine+ recv <- request send+ lift $ putStrLn recv++-- | Get input from a handle one line at a time and send \'@D@\'ownstream+hGetLineD :: Handle -> () -> Server () String IO r+hGetLineD h () = forever $ do+ str <- lift $ hGetLine h+ respond str++-- | Get input from a handle one line at a time and send \'@U@\'pstream+hGetLineU :: Handle -> () -> Client String () IO r+hGetLineU h () = forever $ do+ str <- lift $ hGetLine h+ request str++{-| 'print's all values flowing through it to a 'Handle'++ Prefixes upstream values with \"@U: @\" and downstream values with \"@D: @\"+-}+hPrintB :: (Show a, Show a') => Handle -> a' -> Proxy a' a a' a IO r+hPrintB h = foreverK $ \a' -> do+ lift $ do+ hPutStr h "U: "+ hPrint h a'+ a <- request a'+ lift $ do+ hPutStr h "D: "+ hPrint h a+ respond a++-- | 'print's all values flowing \'@D@\'ownstream to a 'Handle'+hPrintD :: (Show a) => Handle -> x -> Proxy x a x a IO r+hPrintD h = foreverK $ \x -> do+ a <- request x+ lift $ hPrint h a+ respond a++-- | 'print's all values flowing \'@U@\'pstream to a 'Handle'+hPrintU :: (Show a') => Handle -> a' -> Proxy a' x a' x IO r+hPrintU h = foreverK $ \a' -> do+ lift $ hPrint h a'+ x <- request a'+ respond x++{-| 'putStrLn's all values flowing through it to a 'Handle'++ Prefixes upstream values with \"@U: @\" and downstream values with \"@D: @\"+-}+hPutStrLnB :: Handle -> String -> Proxy String String String String IO r+hPutStrLnB h = foreverK $ \a' -> do+ lift $ do+ hPutStr h "U: "+ hPutStrLn h a'+ a <- request a'+ lift $ do+ hPutStr h "D: "+ hPutStrLn h a+ respond a++-- | 'putStrLn's all values flowing \'@D@\'ownstream to a 'Handle'+hPutStrLnD :: Handle -> x -> Proxy x String x String IO r+hPutStrLnD h = foreverK $ \x -> do+ a <- request x+ lift $ hPutStrLn h a+ respond a++-- | 'putStrLn's all values flowing \'@U@\'pstream to a 'Handle'+hPutStrLnU :: Handle -> String -> Proxy String x String x IO r+hPutStrLnU h = foreverK $ \a' -> do+ lift $ hPutStrLn h a'+ x <- request a'+ respond x
+ Control/Proxy/Prelude/Kleisli.hs view
@@ -0,0 +1,53 @@+-- | Utility functions for Kleisli arrows++module Control.Proxy.Prelude.Kleisli (+ -- * Core utility functions+ -- $utility+ foreverK,+ replicateK,+ mapK+ ) where++import Control.Monad (forever, (>=>))+import Control.Monad.Trans.Class (MonadTrans(lift))+import Control.Proxy.Class (Interact(request, respond))+import Data.Closed (C)++{- $utility+ 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+-}++-- | 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 -}++-- | Repeat a \'@K@\'leisli arrow multiple times+replicateK :: (Monad m) => Int -> (a -> m a) -> (a -> m a)+replicateK n k = go n where+ go n+ | n < 1 = return+ | n == 1 = k+ | otherwise = k >=> go (n - 1)++{-| Convenience function equivalent to @(lift .)@++> mapK f >=> mapK g = mapK (f >=> g)+>+> mapK return = return+-}+mapK :: (Monad m, MonadTrans t) => (a -> m b) -> (a -> t m b)+mapK = (lift .)+{-# INLINABLE mapK #-}
+ Control/Proxy/Trans.hs view
@@ -0,0 +1,38 @@+{-| You can define your own extensions to the 'Proxy' type by writing your own+ \"proxy transformers\". Proxy transformers are monad transformers that+ correctly lift 'Proxy' composition from the base monad. Stack multiple+ proxy transformers to chain features together. -}+ +module Control.Proxy.Trans (+ -- * Proxy Transformers+ ProxyTrans(..)+ )where++import Control.Proxy.Class++{-| 'mapP' defines a functor that preserves 'Proxy' composition and Kleisli+ composition.++ Laws:++ * Functor between 'Proxy' categories++> mapP (f <-< g) = mapP f <-< mapP g+> mapP idT = idT++ * Functor between Kleisli categories++> mapP (f <=< g) = mapP f <=< mapP g+> mapP return = return++ Minimal complete definition: 'mapP' or 'liftP'. Defining 'liftP' is more+ efficient.+-}+class ProxyTrans t where+ liftP :: (Monad (p b c d e m), Channel p)+ => p b c d e m r -> t p b c d e m r+ liftP f = mapP (\() -> f) ()++ mapP :: (Monad (p b c d e m), Channel p)+ => (a -> p b c d e m r) -> (a -> t p b c d e m r)+ mapP = (liftP .)
+ Control/Proxy/Trans/Either.hs view
@@ -0,0 +1,128 @@+-- | This module provides the proxy transformer equivalent of 'EitherT'.++{-# LANGUAGE FlexibleContexts, KindSignatures #-}++module Control.Proxy.Trans.Either (+ -- * EitherP+ EitherP(..),+ runEitherK,+ -- * Either operations+ left,+ right,+ -- * Symmetric monad+ -- $symmetry+ throw,+ catch,+ handle+ ) where++import Control.Applicative (Applicative(pure, (<*>)), Alternative(empty, (<|>)))+import Control.Monad (liftM, ap, MonadPlus(mzero, mplus))+import Control.Monad.IO.Class (MonadIO(liftIO))+import Control.Monad.Trans.Class (MonadTrans(lift))+import Control.MFunctor (MFunctor(mapT))+import Control.Proxy.Class (Channel(idT, (>->))) +import Control.Proxy.Trans (ProxyTrans(liftP))+import Prelude hiding (catch)++-- | The 'Either' proxy transformer+newtype EitherP e p a' a b' b (m :: * -> *) r+ = EitherP { runEitherP :: p a' a b' b m (Either e r) }++instance (Monad (p a' a b' b m)) => Functor (EitherP e p a' a b' b m) where+ fmap = liftM++instance (Monad (p a' a b' b m)) => Applicative (EitherP e p a' a b' b m) where+ pure = return+ (<*>) = ap++instance (Monad (p a' a b' b m)) => Monad (EitherP e p a' a b' b m) where+ return = right+ m >>= f = EitherP $ do+ e <- runEitherP m+ runEitherP $ case e of+ Left e -> left e+ Right r -> f r++instance (MonadPlus (p a' a b' b m))+ => Alternative (EitherP e p a' a b' b m) where+ empty = mzero+ (<|>) = mplus++instance (MonadPlus (p a' a b' b m))+ => MonadPlus (EitherP e p a' a b' b m) where+ mzero = EitherP mzero+ mplus m1 m2 = EitherP $ mplus (runEitherP m1) (runEitherP m2)++instance (MonadTrans (p a' a b' b)) => MonadTrans (EitherP e p a' a b' b) where+ lift = EitherP . lift . liftM Right++instance (MonadIO (p a' a b' b m)) => MonadIO (EitherP e p a' a b' b m) where+ liftIO = EitherP . liftIO . liftM Right++instance (MFunctor (p a' a b' b)) => MFunctor (EitherP e p a' a b' b) where+ mapT nat = EitherP . mapT nat . runEitherP++instance (Channel p) => Channel (EitherP e p) where+ idT = EitherP . idT+ p1 >-> p2 = (EitherP .) $ runEitherP . p1 >-> runEitherP . p2++instance ProxyTrans (EitherP e) where+ liftP = EitherP . liftM Right++-- | Run an 'EitherP' \'@K@\'leisi arrow, returning either a 'Left' or 'Right'+runEitherK+ :: (q -> EitherP e p a' a b' b m r) -> (q -> p a' a b' b m (Either e r))+runEitherK = (runEitherP .)++-- | Abort the computation and return a 'Left' result+left :: (Monad (p a' a b' b m)) => e -> EitherP e p a' a b' b m r+left = EitherP . return . Left++-- | Synonym for 'return'+right :: (Monad (p a' a b' b m)) => r -> EitherP e p a' a b' b m r+right = EitherP . return . Right++{- $symmetry+ 'EitherP' forms a second symmetric monad over the left type variable.++ 'throw' is symmetric to 'return'++ 'catch' is symmetric to ('>>=')++ These two functions obey the monad laws:++> catch m throw = m+>+> catch (throw e) f = f e+>+> catch (catch m f) g = catch m (\e -> catch (f e) g)+-}++-- | Synonym for 'left'+throw :: (Monad (p a' a b' b m)) => e -> EitherP e p a' a b' b m r+throw = left++-- | Resume from an aborted operation+catch+ :: (Monad (p a' a b' b m))+ => EitherP e p a' a b' b m r -- ^ Original computation+ -> (e -> EitherP f p a' a b' b m r) -- ^ Handler+ -> EitherP f p a' a b' b m r -- ^ Handled computation+catch m f = EitherP $ do+ e <- runEitherP m+ runEitherP $ case e of+ Left e -> f e+ Right r -> right r++-- | 'catch' with the arguments flipped+handle+ :: (Monad (p a' a b' b m))+ => (e -> EitherP f p a' a b' b m r) -- ^ Handler+ -> EitherP e p a' a b' b m r -- ^ Original computation+ -> EitherP f p a' a b' b m r -- ^ Handled computation+handle f m = EitherP $ do+ e <- runEitherP m+ runEitherP $ case e of+ Left e -> f e+ Right r -> right r
+ Control/Proxy/Trans/Identity.hs view
@@ -0,0 +1,70 @@+-- | This module provides the proxy transformer equivalent of 'IdentityT'.++{-# LANGUAGE FlexibleContexts, KindSignatures #-}++module Control.Proxy.Trans.Identity (+ -- * IdentityP+ IdentityP(..),+ runIdentityK+ ) where++import Control.Applicative (Applicative(pure, (<*>)), Alternative(empty, (<|>)))+import Control.Monad (liftM, ap, MonadPlus(mzero, mplus))+import Control.Monad.IO.Class (MonadIO(liftIO))+import Control.Monad.Trans.Class (MonadTrans(lift))+import Control.MFunctor (MFunctor(mapT))+import Control.Proxy.Class (+ Channel(idT , (>->)), + Interact(request, (\>\), respond, (/>/)) )+import Control.Proxy.Trans (ProxyTrans(liftP))++-- | The 'Identity' proxy transformer+newtype IdentityP p a' a b' b (m :: * -> *) r+ = IdentityP { runIdentityP :: p a' a b' b m r }++instance (Monad (p a' a b' b m)) => Functor (IdentityP p a' a b' b m) where+ fmap = liftM++instance (Monad (p a' a b' b m)) => Applicative (IdentityP p a' a b' b m) where+ pure = return+ (<*>) = ap++instance (Monad (p a' a b' b m)) => Monad (IdentityP p a' a b' b m) where+ return = IdentityP . return+ m >>= f = IdentityP $ runIdentityP m >>= runIdentityP . f++instance (MonadPlus (p a' a b' b m))+ => Alternative (IdentityP p a' a b' b m) where+ empty = mzero+ (<|>) = mplus++instance (MonadPlus (p a' a b' b m))+ => MonadPlus (IdentityP p a' a b' b m) where+ mzero = IdentityP mzero+ mplus m1 m2 = IdentityP $ mplus (runIdentityP m1) (runIdentityP m2)++instance (MonadTrans (p a' a b' b)) => MonadTrans (IdentityP p a' a b' b) where+ lift = IdentityP . lift++instance (MonadIO (p a' a b' b m)) => MonadIO (IdentityP p a' a b' b m) where+ liftIO = IdentityP . liftIO++instance (MFunctor (p a' a b' b)) => MFunctor (IdentityP p a' a b' b) where+ mapT nat = IdentityP . mapT nat . runIdentityP++instance (Channel p) => Channel (IdentityP p) where+ idT = IdentityP . idT+ p1 >-> p2 = (IdentityP .) $ runIdentityP . p1 >-> runIdentityP . p2++instance (Interact p) => Interact (IdentityP p) where+ request = IdentityP . request+ p1 \>\ p2 = (IdentityP .) $ runIdentityP . p1 \>\ runIdentityP . p2+ respond = IdentityP . respond+ p1 />/ p2 = (IdentityP .) $ runIdentityP . p1 />/ runIdentityP . p2++instance ProxyTrans IdentityP where+ liftP = IdentityP++-- | Run an 'IdentityP' \'@K@\'leisli arrow+runIdentityK :: (q -> IdentityP p a' a b' b m r) -> (q -> p a' a b' b m r)+runIdentityK = (runIdentityP .)
+ Control/Proxy/Trans/Maybe.hs view
@@ -0,0 +1,79 @@+-- | This module provides the proxy transformer equivalent of 'MaybeT'.++{-# LANGUAGE FlexibleContexts, KindSignatures #-}++module Control.Proxy.Trans.Maybe (+ -- * MaybeP+ MaybeP(..),+ runMaybeK,+ -- * Maybe operations+ nothing,+ just+ ) where++import Control.Applicative (Applicative(pure, (<*>)), Alternative(empty, (<|>)))+import Control.Monad (liftM, ap, MonadPlus(mzero, mplus))+import Control.Monad.IO.Class (MonadIO(liftIO))+import Control.Monad.Trans.Class (MonadTrans(lift))+import Control.MFunctor (MFunctor(mapT))+import Control.Proxy.Class (Channel(idT, (>->)))+import Control.Proxy.Trans (ProxyTrans(liftP))++-- | The 'Maybe' proxy transformer+newtype MaybeP p a' a b' b (m :: * -> *) r+ = MaybeP { runMaybeP :: p a' a b' b m (Maybe r) }++instance (Monad (p a' a b' b m)) => Functor (MaybeP p a' a b' b m) where+ fmap = liftM++instance (Monad (p a' a b' b m)) => Applicative (MaybeP p a' a b' b m) where+ pure = return+ (<*>) = ap++instance (Monad (p a' a b' b m)) => Monad (MaybeP p a' a b' b m) where+ return = MaybeP . return . Just+ m >>= f = MaybeP $ do+ ma <- runMaybeP m+ runMaybeP $ case ma of+ Nothing -> nothing+ Just a -> f a++instance (Monad (p a' a b' b m)) => Alternative (MaybeP p a' a b' b m) where+ empty = mzero+ (<|>) = mplus++instance (Monad (p a' a b' b m)) => MonadPlus (MaybeP p a' a b' b m) where+ mzero = nothing+ mplus m1 m2 = MaybeP $ do+ ma <- runMaybeP m1+ runMaybeP $ case ma of+ Nothing -> m2+ Just a -> just a++instance (MonadTrans (p a' a b' b)) => MonadTrans (MaybeP p a' a b' b) where+ lift = MaybeP . lift . liftM Just++instance (MonadIO (p a' a b' b m)) => MonadIO (MaybeP p a' a b' b m) where+ liftIO = MaybeP . liftIO . liftM Just++instance (MFunctor (p a' a b' b)) => MFunctor (MaybeP p a' a b' b) where+ mapT nat = MaybeP . mapT nat . runMaybeP++instance (Channel p) => Channel (MaybeP p) where+ idT = MaybeP . idT+ p1 >-> p2 = (MaybeP .) $ runMaybeP . p1 >-> runMaybeP . p2++instance ProxyTrans MaybeP where+ liftP = MaybeP . liftM Just++-- | Run a 'MaybeP' \'@K@\'leisli arrow, returning the result or 'Nothing'+runMaybeK :: (q -> MaybeP p a' a b' b m r) -> (q -> p a' a b' b m (Maybe r))+runMaybeK = (runMaybeP .)++-- | A synonym for 'mzero'+nothing :: (Monad (p a' a b' b m)) => MaybeP p a' a b' b m r+nothing = MaybeP $ return Nothing++-- | A synonym for 'return'+just :: (Monad (p a' a b' b m)) => r -> MaybeP p a' a b' b m r+just = return
+ Control/Proxy/Trans/Reader.hs view
@@ -0,0 +1,105 @@+-- | This module provides the proxy transformer equivalent of 'ReaderT'.++{-# LANGUAGE FlexibleContexts, KindSignatures #-}++module Control.Proxy.Trans.Reader (+ -- * ReaderP+ ReaderP(..),+ runReaderP,+ runReaderK,+ withReaderP,+ -- * Reader operations+ ask,+ local,+ asks,+ ) where++import Control.Applicative (Applicative(pure, (<*>)), Alternative(empty, (<|>)))+import Control.Monad (liftM, ap, MonadPlus(mzero, mplus))+import Control.Monad.IO.Class (MonadIO(liftIO))+import Control.Monad.Trans.Class (MonadTrans(lift))+import Control.MFunctor (MFunctor(mapT))+import Control.Proxy.Class (+ Channel(idT, (>->)), + Interact(request, (\>\), respond, (/>/)) )+import Control.Proxy.Trans (ProxyTrans(liftP))++-- | The 'Reader' proxy transformer+newtype ReaderP i p a' a b' b (m :: * -> *) r+ = ReaderP { unReaderP :: i -> p a' a b' b m r }++instance (Monad (p a' a b' b m)) => Functor (ReaderP i p a' a b' b m) where+ fmap = liftM++instance (Monad (p a' a b' b m)) => Applicative (ReaderP i p a' a b' b m) where+ pure = return+ (<*>) = ap++instance (Monad (p a' a b' b m)) => Monad (ReaderP i p a' a b' b m) where+ return a = ReaderP $ \_ -> return a+ m >>= f = ReaderP $ \i -> do+ a <- unReaderP m i+ unReaderP (f a) i++instance (MonadPlus (p a' a b' b m))+ => Alternative (ReaderP i p a' a b' b m) where+ empty = mzero+ (<|>) = mplus++instance (MonadPlus (p a' a b' b m))+ => MonadPlus (ReaderP i p a' a b' b m) where+ mzero = ReaderP $ \_ -> mzero+ mplus m1 m2 = ReaderP $ \i -> mplus (unReaderP m1 i) (unReaderP m2 i)++instance (MonadTrans (p a' a b' b)) => MonadTrans (ReaderP i p a' a b' b) where+ lift m = ReaderP $ \_ -> lift m++instance (MonadIO (p a' a b' b m)) => MonadIO (ReaderP i p a' a b' b m) where+ liftIO m = ReaderP $ \_ -> liftIO m++instance (MFunctor (p a' a b' b)) => MFunctor (ReaderP i p a' a b' b) where+ mapT nat = ReaderP . fmap (mapT nat) . unReaderP++instance (Channel p) => Channel (ReaderP i p) where+ idT a = ReaderP $ \_ -> idT a+ (p1 >-> p2) a = ReaderP $ \i ->+ ((`unReaderP` i) . p1 >-> (`unReaderP` i) . p2) a++instance (Interact p) => Interact (ReaderP i p) where+ request a = ReaderP $ \_ -> request a+ (p1 \>\ p2) a = ReaderP $ \i ->+ ((`unReaderP` i) . p1 \>\ (`unReaderP` i) . p2) a+ respond a = ReaderP $ \_ -> respond a+ (p1 />/ p2) a = ReaderP $ \i ->+ ((`unReaderP` i) . p1 />/ (`unReaderP` i) . p2) a++instance ProxyTrans (ReaderP i) where+ liftP m = ReaderP $ \_ -> m++-- | Run a 'ReaderP' computation, supplying the environment+runReaderP :: i -> ReaderP i p a' a b' b m r -> p a' a b' b m r+runReaderP i m = unReaderP m i++-- | Run a 'ReaderP' \'@K@\'leisli arrow, supplying the environment+runReaderK :: i -> (q -> ReaderP i p a' a b' b m r) -> (q -> p a' a b' b m r)+runReaderK i = (runReaderP i .)++-- | Modify a computation's environment (a more general version of 'local')+withReaderP+ :: (Monad (p a' a b' b m))+ => (j -> i) -> ReaderP i p a' a b' b m r -> ReaderP j p a' a b' b m r+withReaderP f r = ReaderP $ unReaderP r . f++-- | Get the environment+ask :: (Monad (p a' a b' b m)) => ReaderP i p a' a b' b m i+ask = ReaderP return++-- | Get a function of the environment+asks :: (Monad (p a' a b' b m)) => (i -> r) -> ReaderP i p a' a b' b m r+asks f = ReaderP (return . f)++-- | Modify a computation's environment (a specialization of 'withReaderP')+local+ :: (Monad (p a' a b' b m))+ => (i -> i) -> ReaderP i p a' a b' b m r -> ReaderP i p a' a b' b m r+local = withReaderP
+ Control/Proxy/Trans/State.hs view
@@ -0,0 +1,118 @@+{-| This module provides the proxy transformer equivalent of 'StateT'.++ Sequencing of computations is strict. -}++{-# LANGUAGE FlexibleContexts, KindSignatures #-}++module Control.Proxy.Trans.State (+ -- * StateP+ StateP(..),+ runStateP,+ runStateK,+ evalStateP,+ evalStateK,+ execStateP,+ execStateK,+ -- * State operations+ get,+ put,+ modify,+ gets+ ) where++import Control.Applicative (Applicative(pure, (<*>)), Alternative(empty, (<|>)))+import Control.Monad (liftM, ap, MonadPlus(mzero, mplus))+import Control.Monad.IO.Class (MonadIO(liftIO))+import Control.Monad.Trans.Class (MonadTrans(lift))+import Control.MFunctor (MFunctor(mapT))+import Control.Proxy.Class (Channel(idT, (>->)))+import Control.Proxy.Trans (ProxyTrans(liftP))++-- | The strict 'State' proxy transformer+newtype StateP s p a' a b' b (m :: * -> *) r+ = StateP { unStateP :: s -> p a' a b' b m (r, s) }++instance (Monad (p a' a b' b m)) => Functor (StateP s p a' a b' b m) where+ fmap = liftM++instance (Monad (p a' a b' b m)) => Applicative (StateP s p a' a b' b m) where+ pure = return+ (<*>) = ap++instance (Monad (p a' a b' b m)) => Monad (StateP s p a' a b' b m) where+ return a = StateP $ \s -> return (a, s)+ m >>= f = StateP $ \s -> do+ (a, s') <- unStateP m s+ unStateP (f a) s'++instance (MonadPlus (p a' a b' b m))+ => Alternative (StateP s p a' a b' b m) where+ empty = mzero+ (<|>) = mplus++instance (MonadPlus (p a' a b' b m)) => MonadPlus (StateP s p a' a b' b m) where+ mzero = StateP $ \_ -> mzero+ mplus m1 m2 = StateP $ \s -> mplus (unStateP m1 s) (unStateP m2 s)++instance (MonadTrans (p a' a b' b)) => MonadTrans (StateP s p a' a b' b) where+ lift m = StateP $ \s -> lift $ liftM (\r -> (r, s)) m++instance (MonadIO (p a' a b' b m)) => MonadIO (StateP s p a' a b' b m) where+ liftIO m = StateP $ \s -> liftIO $ liftM (\r -> (r, s)) m++instance (MFunctor (p a' a b' b)) => MFunctor (StateP s p a' a b' b) where+ mapT nat = StateP . fmap (mapT nat) . unStateP++instance (Channel p) => Channel (StateP s p) where+ idT a = StateP $ \_ -> idT a+ (p1 >-> p2) a = StateP $ \s ->+ ((`unStateP` s) . p1 >-> (`unStateP` s) . p2) a++instance ProxyTrans (StateP s) where+ liftP m = StateP $ \s -> liftM (\r -> (r, s)) m++-- | Run a 'StateP' computation, producing the final result and state+runStateP :: s -> StateP s p a' a b' b m r -> p a' a b' b m (r, s)+runStateP s m = unStateP m s++-- | Run a 'StateP' \'@K@\'leisli arrow, procuding the final result and state+runStateK :: s -> (q -> StateP s p a' a b' b m r) -> (q -> p a' a b' b m (r, s))+runStateK s = (runStateP s .)++-- | Evaluate a 'StateP' computation, but discard the final state+evalStateP+ :: (Monad (p a' a b' b m)) => s -> StateP s p a' a b' b m r -> p a' a b' b m r+evalStateP s = liftM fst . runStateP s++-- | Evaluate a 'StateP' \'@K@\'leisli arrow, but discard the final state+evalStateK+ :: (Monad (p a' a b' b m))+ => s -> (q -> StateP s p a' a b' b m r) -> (q -> p a' a b' b m r)+evalStateK s = (evalStateP s .)++-- | Evaluate a 'StateP' computation, but discard the final result+execStateP+ :: (Monad (p a' a b' b m)) => s -> StateP s p a' a b' b m r -> p a' a b' b m s+execStateP s = liftM snd . runStateP s++-- | Evaluate a 'StateP' \'@K@\'leisli arrow, but discard the final result+execStateK+ :: (Monad (p a' a b' b m))+ => s -> (q -> StateP s p a' a b' b m r) -> (q -> p a' a b' b m s)+execStateK s = (execStateP s .)++-- | Get the current state+get :: (Monad (p a' a b' b m)) => StateP s p a' a b' b m s+get = StateP $ \s -> return (s, s)++-- | Set the current state+put :: (Monad (p a' a b' b m)) => s -> StateP s p a' a b' b m ()+put s = StateP $ \_ -> return ((), s)++-- | Modify the current state using a function+modify :: (Monad (p a' a b' b m)) => (s -> s) -> StateP s p a' a b' b m ()+modify f = StateP $ \s -> return ((), f s)++-- | Get the state filtered through a function+gets :: (Monad (p a' a b' b m)) => (s -> r) -> StateP s p a' a b' b m r+gets f = StateP $ \s -> return (f s, s)
+ Control/Proxy/Trans/Tutorial.hs view
@@ -0,0 +1,415 @@+-- | This module provides the tutorial for the "Control.Proxy.Trans" hierarchy++module Control.Proxy.Trans.Tutorial (+ -- * Motivation+ -- $motivation++ -- * Proxy Transformers+ -- $proxytrans++ -- * Compatibility+ -- $compatibility++ -- * Proxy Transformer Stacks+ -- $stacks+ ) where++import Control.Monad.Trans.Class+import Control.Monad.Trans.State+import Control.Proxy+import Control.Proxy.Trans.Either+import Control.Proxy.Trans.State++{- $motivation+ In a 'Session', all composed proxies share effects within the base monad.+ To see how, consider the following simple 'Session':++> client1 :: () -> Client () () (StateT Int IO) r+> client1 () = forever $ do+> s <- lift get+> lift $ lift $ putStrLn $ "Client: " ++ show s+> lift $ put (s + 1)+> request ()+>+> server1 :: () -> Server () () (StateT Int IO) r+> server1 () = forever $ do+> s <- lift get+> lift $ lift $ putStrLn $ "Server: " ++ show s+> lift $ put (s + 1)+> respond ()++>>> execWriterT $ runProxy $ client1 <-< server1+Client: 0+Server: 1+Client: 2+Server: 3+Client: 4+Server: 5+...++ The client and server share the same state, which is sometimes not what we+ want. We can easily solve this by running each 'Proxy' with its own local+ state by changing the order of the 'Proxy' and 'StateT' monad transformers:++> client2 :: () -> StateT Int (Client () () IO) r+> client2 () = forever $ do+> s <- get+> lift $ lift $ putStrLn $ "Client: " ++ show s+> put (s + 1)+> lift $ request ()+>+> server2 :: () -> StateT Int (Server () () IO) r+> server2 () = forever $ do+> s <- get+> lift $ lift $ putStrLn $ "Server: " ++ show s+> put (s + 1)+> lift $ respond ()++ ... but then we can no longer compose them directly. We have to first+ unwrap each one with 'evalStateT' before composing:++>>> runProxy $ (`evalStateT` 0) . client2 <-< (`evalStateT` 0) . server2+Client: 0+Server: 0+Client: 1+Server: 1+Client: 2+Server: 2+...++ Here's another example: suppose we want to handle errors within proxies. We+ could try adding 'EitherT' to the base monad like so:++> import Control.Error+>+> client3 :: () -> Client () () (EitherT String IO) ()+> client3 () = forM_ [1..] $ \i -> do+> lift $ lift $ print i+> request ()+>+> server3 :: (Monad m) => () -> Server () () (EitherT String m) r+> server3 () = lift $ left "ERROR"++>>> runEithert $ runProxy $ client2 <-< server2+1+Left "ERROR"++ Unfortunately, we can't modify @server2@ to 'catchT' that error because we+ cannot access the inner 'EitherT' monad transformer until we run the+ 'Session'. We'd really prefer to place the 'EitherT' monad transformer+ /outside/ the 'Proxy' monad transformer so that we can catch and handle+ errors locally within a 'Proxy' without disturbing other proxies:++> client4 :: () -> EitherT String (Client () () IO) ()+> client4 () = forM_ [1..] $ \i -> do+> lift $ lift $ print i+> lift $ request ()+>+> server4 :: () -> EitherT String (Server () () IO) ()+> server4 () = (forever $ do+> lift $ respond ()+> throwT "Error" )+> `catchT` (\str -> do+> lift $ lift $ putStrLn $ "Caught: " ++ str+> server4 () )++ However, this solution similarly requires unwrapping the client and server+ using 'runEitherT' before composing them:++>>> runProxy $ runEitherT . client4 <-< runEitherT . server4+1+Caught: Error+2+Caught: Error+3+Caught: Error+...++-}++{- $proxytrans+ We need some way to layer monad transformers /outside/ the proxy type+ without interfering with 'Proxy' composition. To do this, we overload+ 'Proxy' composition using the 'Channel' type class from+ "Control.Proxy.Class":++> class Channel p where+> idT :: (Monad) m => a' -> p a' a a' a m r+> (>->)+> :: (Monad m)+> => (b' -> p a' a b' b m r)+> -> (c' -> p b' b c' c m r)+> -> (c' -> p a' a c' c m r)++ Obviously, 'Proxy' implements this class:++> instance Channel Proxy where ...++ ... but we would also like our monad transformers layered outside the+ 'Proxy' type to also implement the 'Channel' class so that we could compose+ them directly without unwrapping. Unfortunately, these monad transformers+ do not fit the signature of the 'Channel' class.++ Fortunately, the "Control.Proxy.Trans" hierarchy provides several common+ monad transformers which have been upgraded to fit the 'Channel' type class.+ I call these \"proxy transformers\".++ For example, "Control.Proxy.Trans.State" provides a proxy transformer+ equivalent to @Control.Monad.Trans.State@. Similarly,+ "Control.Proxy.Trans.Either" provides a proxy transformer equivalent to+ @Control.Monad.Trans.Either@.++ Let's use a working code example to demonstrate how to use them:++> import Control.Proxy.Trans.State+> +> client5 :: () -> StateP Int Proxy () () () C IO r+> client5 () = forever $ do+> s <- get+> liftP $ lift $ putStrLn $ "Client: " ++ show s+> put (s + 1)+> liftP $ request ()+>+> server5 :: () -> StateP Int Proxy C () () () IO r+> server5 () = forever $ do+> s <- get+> liftP $ lift $ putStrLn $ "Server: " ++ show s+> put (s + 1)+> liftP $ respond ()++ You'll see that our type signatures changed. Now we use 'StateP' instead of+ 'StateT'. However, 'StateP' does not transform monads, but instead+ transforms proxies.++ To see this, let's first study the kind of 'StateT'. If we first define:++> kind MonadKind = * -> *++ Then @StateT s@ takes a monad, and returns a new monad:++> StateT s :: MonadKind -> MonadKind++ Now consider the kind of a 'Proxy'-like type constructor suitable for the+ 'Channel' type class:++> kind ProxyKind = * -> * -> * -> * -> (* -> *) -> * -> *++ Then @StateP s@ takes a 'Proxy'-like and returns a new 'Proxy'-like type:++> StateP s :: ProxyKind -> ProxyKind++ This is why I call these \"proxy transformers\" and not monad transformers.+ They all take some 'Proxy'-like type that implements 'Channel' and transform+ it into a new 'Proxy'-like type that also implements 'Channel'. For+ example, 'StateP' implement the following instance:++> instance (Channel p) => Channel (StateP s p) where ...++ All proxy transformers guarantee that if the base proxy implements the+ 'Channel' type class, then the transformed proxy also implements the+ 'Channel' type class. This means that you can build a proxy transformer+ stack, just like you might build a monad transformer stack.++ Unfortunately, in order to use proxy transformers, you must expand out the+ 'Client' and 'Server' type synonyms, which are not compatible with proxy+ transformers. Sorry! This is why there are no 'Server' or 'Client' type+ synonyms in the types of our new client and server and I had to write out+ all the inputs and outputs.++ Notice how the outermost 'lift' statements in our client and server have+ changed to 'liftP'. 'liftP' replaces 'lift' for proxy transformers, and it+ lifts any action in the base proxy to an action in the transformed proxy.+ In the previous example, the base proxy was 'Proxy' and the transformed+ proxy was @StateP s Proxy@, so 'liftP's type got specialized to:++> liftP :: Proxy a' a b' b m r -> StateP s Proxy a' a b' b m r++ The 'ProxyTrans' class defines 'liftP', and all proxy transformers implement+ the 'ProxyTrans' class. Since proxies are still monads, 'liftP' must+ behave just like 'lift' and obey the monad transformer laws:++> (liftP .) return = return+>+> (liftP .) (f >=> g) = (liftP .) f >=> (liftP .) g++ But, unlike 'lift', 'liftP' obeys one extra set of laws that guarantee it + also lifts composition sensibly:++> (liftP .) idT = idT+>+> (liftP .) (f >-> g) = (liftP .) f >-> (liftP .) g++ In fact, this @(liftP .)@ pattern is so ubiquitous, that the 'ProxyTrans'+ class provides the additional 'mapP' method for convenience:++> mapP = (liftP .)++ Proxy transformers automatically derive how to lift composition correctly+ and also guarantee that the derived composition obeys the category laws if+ the base composition obeyed the category laws. Since 'Proxy' composition+ obeys the category laws, any proxy transformer stack built on top of it+ automatically derives a composition operation that is correct by+ construction.++ Let's prove this by directly composing our 'StateP'-extended proxies without+ unwrapping them:++> :t client5 <-< server5 :: () -> StateP Int Proxy C () () C IO r++ However, we still have to unwrap the final 'StateP' 'Session' before we can+ pass it to 'runProxy'. We use 'runStateK' for this purpose:++>>> runProxy $ runStateK 0 $ client5 <-< server5+Client: 0+Server: 0+Client: 1+Server: 1+Client: 2+Server: 2+Client: 3+Server: 3+...++ Keep in mind that 'runStateK' takes the initial state as its first argument,+ unlike 'runStateT'. I break from the @transformers@ convention for+ syntactic convenience.++ We can similarly fix our 'EitherT' example, using 'EitherP' from+ "Control.Proxy.Trans.Either":++> import Control.Proxy.Trans.Either as E+>+> client6 :: () -> EitherP String Proxy () () () C IO ()+> client6 () = forM_ [1..] $ \i -> do+> liftP $ lift $ print i+> liftP $ request ()+>+> server6 :: () -> EitherP String Proxy C () () () IO ()+> server6 () = (forever $ do+> liftP $ respond ()+> E.throw "Error" )+> `E.catch` (\str -> do+> liftP $ lift $ putStrLn $ "Caught: " ++ str+> server6 () )++>>> runProxy $ runEitherK $ client6 <-< server6+1+Caught: Error+2+Caught: Error+3+Caught: Error+...++-}++{- $compatibility+ Proxy transformers do more than just lift composition. They automatically+ promote proxies written in the base monad. For example, what if I wanted to+ use the 'takeB_' proxy from "Control.Proxy.Prelude.Base" to cap the number+ of results? I can't compose it directly because it uses the 'Proxy' type:++> takeB_ :: (Monad m) => Int -> a' -> Proxy a' a a' a m ()++ ... whereas @client6@ and @server6@ use @EitherP String Proxy@. However,+ this doesn't matter because we can automatically lift 'takeB_' to be+ compatible with them using 'mapP':++>>> runProxy $ runEitherK $ client6 <-< mapP (takeB_ 2) <-< server6+1+Caught: Error+2+Caught:Error++ 'mapP' promotes any proxy written using the base proxy type to automatically+ be compatible with proxies written using the extended proxy type. This+ means you can safely write utility proxies using the smallest feature set+ they require and promote them as necessary to work with more extended+ feature sets. This ensures that any proxies you write always remain+ forwards-compatible as people write new extensions.+-}++{- $stacks+ You can stack proxy transformers to combine their effects, such as in the+ following example, which combines everything we've used so far:++> client7 :: () -> EitherP String (StateP Int Proxy) () Int () C IO r+> client7 () = do+> n <- liftP get+> liftP $ liftP $ lift $ print n+> n' <- liftP $ liftP $ request ()+> liftP $ put n'+> E.throw "ERROR"++>>> runProxy $ runStateK 0 $ runEitherK $ client7 <-< mapP (mapP (enumFromS 1))+0+(Left "Error", 1)++ But that's still not the full story! For calls to the base monad (i.e. 'IO'+ in this case), you don't need to precede them with all those 'liftP's.+ Every proxy transformer also correctly derives 'MonadTrans', so you can dig+ straight to the base monad by just calling 'lift' at the outer-most level:++> client7 :: () -> EitherP String (StateP Int Proxy) () Int () C IO r+> client7 () = do+> n <- liftP get+> lift $ print n -- Much better!+> n' <- liftP $ liftP $ request ()+> liftP $ put n'+> E.throw "ERROR"++ Also, you can combine multiple proxy transformers into a single proxy+ transformer, just like you would with monad transformers:++> newtype BothP e s p a' a b' b m r =+> BothP { unBothP :: EitherP e (StateP s p) a' a b' b m r }+> deriving (Functor, Applicative, Monad, MonadTrans, Channel)+> +> instance ProxyTrans (BothP e s) where+> liftP = BothP . liftP . liftP+> +> runBoth+> :: (Monad m)+> => s+> -> (b' -> BothP e s p a' a b' b m r)+> -> (b' -> p a' a b' b m (Either e r, s))+> runBoth s = runStateK s . runEitherK . fmap unBothP+> +> get' :: (Monad (p a' a b' b m), Channel p)+> => BothP e s p a' a b' b m s+> get' = BothP $ liftP get+> +> put' :: (Monad (p a' a b' b m), Channel p)+> => s -> BothP e s p a' a b' b m ()+> put' x = BothP $ liftP $ put x+> +> throw' :: (Monad (p a' a b' b m), Channel p)+> => e -> BothP e s p a' a b' b m r+> throw' e = BothP $ E.throw e++ Then we can write proxies using this new proxy transformer of ours:++> client8 :: () -> BothP String Int Proxy () Int () C IO r+> client8 () = do+> n <- get'+> lift $ print n+> n' <- liftP $ request ()+> put' n'+> throw' "ERROR"++>>> runProxy $ runBoth 0 $ client8 <-< mapP (enumFromS 1)+0+(Left "ERROR",1)++ Note that 'request' and 'respond' are not automatically liftable, because of+ technical limitations with Haskell type classes. When I resolve these+ issues they will also be automatically promoted by proxy transformers. For+ now, you must lift them manually using 'liftP':++> request = (liftP .) request+> respond = (liftP .) respond++ The left 'request' and 'respond' in the above equations are what the lifted+ definitions would be for each proxy transformer if Haskell's type class+ system didn't get in my way.+-}
+ Control/Proxy/Trans/Writer.hs view
@@ -0,0 +1,111 @@+{-| This module provides the proxy transformer equivalent of 'WriterT'.++ This module is even stricter than @Control.Monad.Trans.Writer.Strict@ by+ being strict in the accumulated monoid. ++ The underlying implementation uses the state monad to avoid quadratic blowup+ from left-associative binds. -}++{-# LANGUAGE FlexibleContexts, KindSignatures #-}++module Control.Proxy.Trans.Writer (+ -- * WriterP+ WriterP(..),+ runWriterP,+ runWriterK,+ execWriterP,+ execWriterK,+ -- * Writer operations+ tell,+ censor+ ) where++import Control.Applicative (Applicative(pure, (<*>)), Alternative(empty, (<|>)))+import Control.Monad (liftM, ap, MonadPlus(mzero, mplus))+import Control.Monad.IO.Class (MonadIO(liftIO))+import Control.Monad.Trans.Class (MonadTrans(lift))+import Control.MFunctor (MFunctor(mapT))+import Control.Proxy.Class (Channel(idT, (>->)))+import Control.Proxy.Trans (ProxyTrans(liftP))+import Data.Monoid (Monoid(mempty, mappend))++-- | The strict 'Writer' proxy transformer+newtype WriterP w p a' a b' b (m :: * -> *) r+ = WriterP { unWriterP :: w -> p a' a b' b m (r, w) }++instance (Monad (p a' a b' b m))+ => Functor (WriterP w p a' a b' b m) where+ fmap = liftM++instance (Monad (p a' a b' b m))+ => Applicative (WriterP w p a' a b' b m) where+ pure = return+ (<*>) = ap++instance (Monad (p a' a b' b m))+ => Monad (WriterP w p a' a b' b m) where+ return a = WriterP $ \w -> return (a, w)+ m >>= f = WriterP $ \w -> do+ (a, w') <- unWriterP m w+ unWriterP (f a) w'++instance (MonadPlus (p a' a b' b m))+ => Alternative (WriterP w p a' a b' b m) where+ empty = mzero+ (<|>) = mplus++instance (MonadPlus (p a' a b' b m))+ => MonadPlus (WriterP w p a' a b' b m) where+ mzero = WriterP $ \w -> mzero+ mplus m1 m2 = WriterP $ \w -> mplus (unWriterP m1 w) (unWriterP m2 w)++instance (MonadTrans (p a' a b' b))+ => MonadTrans (WriterP w p a' a b' b) where+ lift m = WriterP $ \w -> lift $ liftM (\r -> (r, w)) m++instance (MonadIO (p a' a b' b m))+ => MonadIO (WriterP w p a' a b' b m) where+ liftIO m = WriterP $ \w -> liftIO $ liftM (\r -> (r, w)) m++instance (MFunctor (p a' a b' b)) => MFunctor (WriterP w p a' a b' b) where+ mapT nat = WriterP . fmap (mapT nat) . unWriterP++instance (Channel p) => Channel (WriterP w p) where+ idT a = WriterP $ \_ -> idT a+ (p1 >-> p2) a = WriterP $ \w ->+ ((`unWriterP` w) . p1 >-> (`unWriterP` w) . p2) a++instance (Monoid w) => ProxyTrans (WriterP w) where+ liftP m = WriterP $ \w -> liftM (\r -> (r, w)) m++-- | Run a 'WriterP' computation, producing the final result and monoid+runWriterP :: (Monoid w) => WriterP w p a' a b' b m r -> p a' a b' b m (r, w)+runWriterP p = unWriterP p mempty++-- | Run a 'WriterP' \'@K@\'leisli arrow, producing the final result and monoid+runWriterK+ :: (Monoid w)+ => (q -> WriterP w p a' a b' b m r) -> (q -> p a' a b' b m (r, w))+runWriterK = (runWriterP . )++-- | Evaluate a 'WriterP' computation, but discard the final result+execWriterP+ :: (Monad (p a' a b' b m), Monoid w)+ => WriterP w p a' a b' b m r -> p a' a b' b m w+execWriterP m = liftM snd $ runWriterP m++-- | Evaluate a 'WriterP' \'@K@\'leisli arrow, but discard the final result+execWriterK+ :: (Monad (p a' a b' b m), Monoid w)+ => (q -> WriterP w p a' a b' b m r) -> (q -> p a' a b' b m w)+execWriterK = (execWriterP .)++-- | Add a value to the monoid+tell :: (Monad (p a' a b' b m), Monoid w) => w -> WriterP w p a' a b' b m ()+tell w' = WriterP $ \w -> let w'' = mappend w w' in w'' `seq` return ((), w'')++-- | Modify the result of a writer computation+censor+ :: (Monad (p a' a b' b m), Monoid w)+ => (w -> w) -> WriterP w p a' a b' b m r -> WriterP w p a' a b' b m r+censor f = WriterP . fmap (liftM (\(a, w) -> (a, f w))) . unWriterP
Control/Proxy/Tutorial.hs view
@@ -13,13 +13,16 @@ -- * Idioms -- $idioms - -- * The importance of compositionality- -- $compose+ -- * Reusability+ -- $reuse -- * Mixing monads and composition -- $monads - -- * Pipe Compatibility+ -- * Utility proxies+ -- $utility++ -- * Pipe compatibility -- $pipes ) where @@ -87,9 +90,9 @@ > 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:+ can run in the 'IO' monad. We run it using the the 'runProxy' function: ->>> runSession session :: IO ()+>>> runProxy session :: IO () Client requested: 1 Server received : 1 Server responded: 2@@ -132,7 +135,7 @@ We can see if our proxy does its job correctly: ->>> runSession $ oneTwoThree <-< malicious <-< incrementer+>>> runProxy $ oneTwoThree <-< malicious <-< incrementer Client requested: 1 Server received : 1 Server responded: 2@@ -152,7 +155,7 @@ We can also add more proxies as we see fit: ->>> runSession $ oneTwoThree <-< malicious <-< malicious <-< incrementer +>>> runProxy $ oneTwoThree <-< malicious <-< malicious <-< incrementer Client requested: 1 Server received : 1 Server responded: 2@@ -178,21 +181,18 @@ '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+> type Server arg ret = Proxy C () arg ret+> type Client arg ret = Proxy arg ret () C 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+ also a 'Proxy', one with both ends closed: - A 'Session' is just a 'Proxy' that has neither an upstream interface nor a- downstream interface.+> type Session = Proxy C () () C - 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.+ The 'Proxy' is the unifying type 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:@@ -213,13 +213,13 @@ > +- 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 ()+> incrementer :: Int -> Proxy C () Int Int IO r+> malicious :: Int -> Proxy Int Int Int Int IO r+> oneTwoThree :: () -> Proxy Int Int () C IO () >-> session :: () -> Proxy Void () () Void IO ()+> session :: () -> Proxy C () () C IO () - Composition supplies the first request through this initial parameters+ Composition supplies the first request through this initial parameter and all subsequent requests are bound to 'respond' statements. This means that the actual types you compose are all of the form:@@ -275,7 +275,7 @@ > someProxy arg = do > ... > nextArg <- respond x-> someProxy nexArg+> someProxy nextArg "Control.Proxy" provides the 'foreverK' utility function which abstracts away this manual recursion:@@ -314,11 +314,11 @@ > -- = request >=> respond >=> request >=> respond >=> ... -} -{- $compose+{- $reuse 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:+ replace our client with a command line prompt where the user provides the+ input to the server: > inputPrompt :: (Read a, Show b) => () -> Client a b IO r > inputPrompt () = forever $ do@@ -328,7 +328,7 @@ > lift $ print b > lift $ putStrLn "*" ->>> runSession $ inputPrompt <-< incrementer+>>> runProxy $ inputPrompt <-< incrementer 42<Enter> Server received : 42 Server responded: 43@@ -350,7 +350,7 @@ > lift $ putStrLn $ "Client received : " ++ show b > respond b ->>> runSession $ inputPrompt <-< diagnoseClient <-< incrementer+>>> runProxy $ inputPrompt <-< diagnoseClient <-< incrementer 42<Enter> Client requested: 42 Server received : 42@@ -373,7 +373,7 @@ > verboseInput :: (Read a, Show b, Show a) => () -> Client a b IO r > verboseInput = inputPrompt <-< diagnoseClient ->>> runSession $ verboseInput <-< incrementer+>>> runProxy $ verboseInput <-< incrementer <Exactly same behavior> Or what if I want to cache the results coming out of @incrementer@? I can@@ -395,7 +395,7 @@ > a' <- respond b > cache' m a' ->>> runSession $ verboseInput <-< cache <-< incrementer +>>> runProxy $ verboseInput <-< cache <-< incrementer 42<Enter> Client requested: 42 Server received : 42@@ -410,9 +410,9 @@ 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':+ 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 @@ -435,8 +435,10 @@ > oneTwoThree () > -- Here we bind composition within a larger do block > (inputPrompt <-< cache) ()+>+> -- or: mixedClient = oneTwoThree >=> (inputPrompt <-< cache) ->>> runSession $ mixedClient <-< incrementer+>>> runProxy $ mixedClient <-< incrementer Client requested: 1 Server received : 1 Server responded: 2@@ -463,10 +465,69 @@ * So feel free to use your imagination! Up until the moment you call- 'runSession', you can freely mix composition or @do@ notation within each+ 'runProxy', you can freely mix composition or @do@ notation within each other. -} +{- $utility+ This library features several utility proxies to get you started. They all+ reside under the "Control.Proxy.Prelude" hierarchy and they are imported by+ default when you import "Control.Proxy".++ For example, if you wanted to print the first 3 natural numbers, you would+ use:++>>> runProxy $ printD <-< enumFromToS 1 3+1+2+3++ The utility functions follow a systematic naming convention that uses the+ last letter:++ * @D@: Only interacts with values going \'@D@\'ownstream towards the+ 'Client'++ * @U@: Only interacts with values going \'@U@\'pstream towards the 'Server'++ * @B@: Interacts with values going \'@B@\'oth ways++ * @C@: Belongs in the \'@C@\'lient position++ * @S@: Belongs in the \'@S@\'server position++ Many utility proxies auto-forward values they receive, such as 'printD'.+ This means we can easily combine multiple handling stages for processing+ values:++> import Control.Proxy+> import System.IO+>+> main = do+> h <- openFile "test.txt" WriteMode+> runProxy $ hPrintD h <-< printD <-< enumFromToS 1 3+> hClose h++>>> main+1+2+3++ The above program also wrote the same output to the file "test.txt":++> $ cat test.txt+> 1+> 2+> 3++ 'runProxy' discards any output that goes past the endpoints of the session,+ so you don't need to worry about closing off each end.++ This library does not provide 'ByteString' or 'Text' utilities in order to+ reduce the number of dependencies of the main package. These will be+ released in a separate package in the near future.+-}+ {- $pipes 'Proxy's generalize 'Pipe's by permitting communication upstream. Fortunately, though, you don't need to rewrite your code if you have already@@ -480,13 +541,10 @@ necessary. To understand how 'Pipe's map onto 'Proxy's, just check out the 'Pipe'- definition in "Control.Proxy":+ definition in "Control.Proxy.Pipe": > 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.+ information upstream. -}
+ Data/Closed.hs view
@@ -0,0 +1,8 @@+{-| An empty type that gives cleaner type signatures. -}++module Data.Closed (+ -- * Closed+ C ) where++-- | The empty type, denoting a \'@C@\'losed end+data C
pipes.cabal view
@@ -1,5 +1,5 @@ Name: pipes-Version: 2.3.0+Version: 2.4.0 Cabal-Version: >=1.14.0 Build-Type: Simple License: BSD3@@ -17,36 +17,29 @@ . Advantages over traditional iteratee implementations: .- * /Simpler semantics/: There is only one data type ('Pipe'), two primitives- ('await' and 'yield'), and only one way to compose 'Pipe's ('.'). In fact,- this library implements its entire behavior using its 'Monad' and 'Category'- instances and enforces their laws strictly!+ * /Concise API/: This library uses a few simple abstractions with a very high+ power-to-weight ratio to reduce adoption time. .- * /Clearer naming conventions/: Enumeratees are called 'Pipe's, Enumerators- are 'Producer's, and Iteratees are 'Consumer's. 'Producer's and 'Consumer's- are just type synonyms for 'Pipe's with either the input or output end- closed.+ * /Clear semantics/: All abstractions are grounded in category theory, which+ leads to intuitive behavior (and fewer bugs, if any!). .- * /Pipes are Categories/: You compose them using ordinary composition.+ * /Bidirectionality/: The library provides a bidirectional type, called a+ 'Proxy'. .- * /Intuitive/: Pipe composition is easier to reason about because it is a true- 'Category'. Composition works seamlessly and you don't have to worry about- restarting iteratees, feeding new input, etc. \"It just works\".+ * /Extension Framework/: You can elegantly mix and match extensions to the+ base type and easily create your own! .- * /"Vertical" concatenation works flawlessly on everything/: ('>>')- concatenates 'Pipe's, but since everything is a 'Pipe', you can use it to- concatenate 'Producer's, 'Consumer's, and even intermediate 'Pipe' stages.- Vertical Concatenation always works the way you expect, picking up where the- previous 'Pipe' left off.+ * /Extensive Documentation/: Second to none! .- * /Bidirectionality/: The library now provides a bidirectional 'Pipe' type,- called a 'Proxy'.+ I recommend you begin by reading "Control.Pipe.Tutorial" which introduces the+ basic concepts using the simpler 'Pipe' API. Then move on to+ "Control.Proxy.Tutorial", which introduces the 'Proxy' type which forms the+ core abstraction of this library. To use extensions or define your own, check+ out "Control.Proxy.Trans.Tutorial". .- Check out "Control.Pipe.Tutorial" for a copious introductory tutorial and- "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+ I will soon replace "Control.Frame" with a superior resource-management+ solution, so new users of the library should avoid using it.+Category: Control, Pipe, Proxies Tested-With: GHC ==7.4.1 Source-Repository head Type: git@@ -55,17 +48,34 @@ Library Build-Depends: base >= 4 && < 5,+ free >= 3.2, index-core,- transformers,- transformers-free,- void+ transformers Exposed-Modules: Control.Frame, Control.Frame.Tutorial, Control.IMonad.Trans.Free,+ Control.MFunctor, Control.Proxy,+ Control.Proxy.Core,+ Control.Proxy.Class,+ Control.Proxy.Pipe,+ Control.Proxy.Trans,+ Control.Proxy.Trans.Either,+ Control.Proxy.Trans.Identity,+ Control.Proxy.Trans.Maybe,+ Control.Proxy.Trans.Reader,+ Control.Proxy.Trans.State,+ Control.Proxy.Trans.Tutorial,+ Control.Proxy.Trans.Writer, Control.Proxy.Tutorial,+ Control.Proxy.Prelude,+ Control.Proxy.Prelude.Base,+ Control.Proxy.Prelude.IO,+ Control.Proxy.Prelude.Kleisli, Control.Pipe,- Control.Pipe.Tutorial+ Control.Pipe.Core,+ Control.Pipe.Tutorial,+ Data.Closed GHC-Options: -O2 Default-Language: Haskell2010