pipes 1.0.1 → 1.0.2
raw patch · 3 files changed
+40/−53 lines, 3 filesdep +voidPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: void
API changes (from Hackage documentation)
- Control.Pipe.Common: data Zero
+ Control.Pipe.Common: Await :: (a -> Pipe a b m r) -> Pipe a b m r
+ Control.Pipe.Common: M :: (m (Pipe a b m r)) -> Pipe a b m r
+ Control.Pipe.Common: Pure :: r -> Pipe a b m r
+ Control.Pipe.Common: Yield :: (b, Pipe a b m r) -> Pipe a b m r
- Control.Pipe.Common: type Consumer a m r = Pipe a Zero m r
+ Control.Pipe.Common: type Consumer a m r = Pipe a Void m r
- Control.Pipe.Common: type Pipeline m r = Pipe Zero Zero m r
+ Control.Pipe.Common: type Pipeline m r = Pipe () Void m r
- Control.Pipe.Common: type Producer b m r = Pipe Zero b m r
+ Control.Pipe.Common: type Producer b m r = Pipe () b m r
Files
- Control/Pipe.hs +32/−35
- Control/Pipe/Common.hs +6/−16
- pipes.cabal +2/−2
Control/Pipe.hs view
@@ -35,58 +35,60 @@ > take' :: (Monad m) => Int -> Pipe a a m () - 'Pipe's are conservative about using the base monad. In fact, you can only- invoke the base monad by using the 'lift' function from 'Pipe''s- 'MonadTrans' instance. If you never use 'lift', your 'Pipe' will translate- into pure code.+ 'Pipe's use the base monad conservatively. In fact, you can only invoke the+ base monad by using the 'lift' function from 'Pipe''s 'MonadTrans' instance. If you never use 'lift', your 'Pipe' will translate into pure code. Now let's create a function that converts a list into a 'Pipe' by 'yield'ing each element of the list: -> fromList :: (Monad m) => [a] -> Pipe Zero a m ()+> fromList :: (Monad m) => [a] -> Pipe () a m () > fromList = mapM_ yield - The 'Zero' in the type signature represents a type with no constructors- and we use it to block the input end of the 'Pipe' so that it can't request- any input from an upstream 'Pipe'. You can think of @fromList@ as a one way- 'Pipe' that can only deliver output, which makes it suitable for the first- stage in a 'Pipeline'. I provide a type synonym for this common case:+ Note that @fromList xs@ has an input type of @()@. Ideally, we would like+ to guarantee at a type level that @fromList@ will not call 'await', however+ this is impossible. No choice of an input type forbids a 'Pipe' from+ calling 'await'. However, we can set the input type to @()@ so that we can+ trivially satisfy any await request by feeding it a @()@. -> type Producer b m r = Pipe Zero b m r+ By setting a Pipe's input to @()@, we block it from receiving any (useful)+ input. Such a pipe can only deliver output, which makes it suitable for the+ first stage in a 'Pipeline'. I provide a type synonym for this common case: - You can then rewrite the type signature for @fromList@ as:+> type Producer b m r = Pipe () b m r + 'Producer's resemble enumerators in other libraries because they are a+ data source. You can then use the 'Producer' type synonym to rewrite the+ type signature for @fromList@ as:+ > fromList :: (Monad m) => [a] -> Producer a m () - Note that you don't have to block the input end with the 'Zero' type. If+ Note that you don't have to block the input end with the @()@ type. If you let the compiler infer the type, you would get: -> fromList :: (Monad m) => [a] -> Pipe b a m ()-- The compiler says that the input could be anything since without any calls- to 'await' it can't infer the input type. I only provide the 'Zero' type- as a convenience so that you can intentionally block 'Pipe' ends.+> fromList :: (Monad m) => [a] -> Pipe t a m () - 'Producer's resemble enumerators in other libraries because they are a data- source. 'Producer's never use 'await' statements.+ The compiler correctly infers that the input could be anything since it is+ never used. This more polymorphic type signature is suitable, but you can+ set the input to @()@ to ensure that you do not inadvertently attach a+ useful pipe upstream. Now let's create a 'Pipe' that prints every value delivered to it and never terminates: -> printer :: (Show a) => Pipe a Zero IO b+> printer :: (Show a) => Pipe a Void IO r > printer = forever $ do > x <- await > lift $ print x - The 'Zero' in @printer@'s type signature indicates that it never delivers+ The 'Void' in @printer@'s type signature indicates that it never delivers output downstream, so it represents the final stage in a 'Pipeline'. Again, I provide a type synonym for this common case: -> type Consumer a m r = Pipe a Zero m r+> type Consumer a m r = Pipe a Void m r So we could instead write @printer@'s type as: -> printer :: (Show a) => Consumer a IO b+> printer :: (Show a) => Consumer a IO r 'Consumer's resemble iteratees in other libraries because they are a data sink. 'Consumer's never use 'yield' statements.@@ -106,15 +108,15 @@ For example, you can compose the above 'Pipe's with: -> pipeline :: Pipe Zero Zero IO ()-> pipeline :: unLazy $ Lazy printer . Lazy (take' 3) . Lazy (fromList [1..])+> pipeline :: Pipe () Void IO ()+> pipeline = unLazy $ Lazy printer . Lazy (take' 3) . Lazy (fromList [1..]) The compiler deduces that the final 'Pipe' must be blocked at both ends,- meaning it will never 'await' any input and it will never 'yield' any+ meaning it will never 'await' useful input and it will never 'yield' any output. This represents a self-contained 'Pipeline' and I provide a type synonym for this common case: -> type Pipeline m r = Pipe Zero Zero m r+> type Pipeline m r = Pipe () Void m r Also, I provide convenience operators for composing 'Pipe's without the burden of wrapping and unwrapping newtypes. For example, to compose 'Pipe's@@ -133,13 +135,8 @@ > runPipe :: (Monad m) => Pipeline m r -> m r - 'runPipe' only works on self-contained 'Pipeline's. This is the only- function in the entire library that actually requires the 'Zero' type- because it must guarantee that its argument 'Pipe' will never try to- 'await' or 'yield'. You don't need to worry about explicitly giving it- capped 'Pipe's because self-contained 'Pipe's will automatically have- polymorphic input and output ends and 'runPipe' will just assume those ends- are 'Zero'.+ 'runPipe' only works on self-contained 'Pipeline's. You don't need to worry about explicitly giving it blocked 'Pipe's because self-contained pipelines+ will automatically have polymorphic input and output ends. Let's try using 'runPipe':
Control/Pipe/Common.hs view
@@ -1,7 +1,6 @@ module Control.Pipe.Common ( -- * Types- Pipe,- Zero,+ Pipe(..), Producer, Consumer, Pipeline,@@ -97,6 +96,7 @@ import Control.Category import Control.Monad import Control.Monad.Trans+import Data.Void import Prelude hiding ((.), id) {-|@@ -159,22 +159,14 @@ instance MonadTrans (Pipe a b) where lift = M . liftM pure --- | A data type with no exposed constructors-data Zero = Zero-{- I'm not quite sure that this is the correct approach. I also considered- using "()" or universal quantification (i.e. Producer b m r =- forall a . Pipe a b m r). What I really want is some way to provide runPipe- some compile-time guarantee that its argument Pipe has no residual await or- yield statements. -}- -- | A pipe that can only produce values-type Producer b m r = Pipe Zero b m r+type Producer b m r = Pipe () b m r -- | A pipe that can only consume values-type Consumer a m r = Pipe a Zero m r+type Consumer a m r = Pipe a Void m r -- | A self-contained pipeline that is ready to be run-type Pipeline m r = Pipe Zero Zero m r+type Pipeline m r = Pipe () Void m r {-| Wait for input from upstream within the 'Pipe' monad:@@ -254,7 +246,5 @@ runPipe p' = case p' of Pure r -> return r M mp -> mp >>= runPipe- -- Technically a blocked Pipe can still await- Await f -> runPipe $ f Zero- -- A blocked Pipe can not yield, but I include this as a precaution+ Await f -> runPipe $ f () Yield (_, p) -> runPipe p
pipes.cabal view
@@ -1,5 +1,5 @@ Name: pipes-Version: 1.0.1+Version: 1.0.2 Cabal-Version: >=1.10.1 Build-Type: Simple License: BSD3@@ -60,7 +60,7 @@ Location: https://github.com/Gabriel439/Haskell-Pipes-Library Library- Build-Depends: base >= 4 && < 5, mtl+ Build-Depends: base >= 4 && < 5, mtl, void Exposed-Modules: Control.Pipe, Control.Pipe.Common GHC-Options: -O2 Default-Language: Haskell2010