pipes-core (empty) → 0.0.1
raw patch · 10 files changed
+1397/−0 lines, 10 filesdep +basedep +categoriesdep +lifted-basesetup-changed
Dependencies added: base, categories, lifted-base, monad-control, mtl, void
Files
- Control/Category/Multiplicative.hs +40/−0
- Control/Pipe.hs +450/−0
- Control/Pipe/Category.hs +76/−0
- Control/Pipe/Combinators.hs +165/−0
- Control/Pipe/Common.hs +330/−0
- Control/Pipe/Exception.hs +116/−0
- Control/Pipe/Monoidal.hs +128/−0
- LICENSE +25/−0
- Setup.hs +2/−0
- pipes-core.cabal +65/−0
+ Control/Category/Multiplicative.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE MultiParamTypeClasses #-}++-- | This module contains Multiplicative and Comultiplicative type classes,+-- which provide a generalization of 'splitP' and 'joinP' to arbitrary monoidal+-- categories.+module Control.Category.Multiplicative where++import Control.Category.Monoidal++-- | Monoidal category with a multiplication natural transformation.+--+-- A multiplicative structure on @k@ is the same thing as a monoid object+-- structure on the identity functor, when End(k) is given the pointwise+-- monoidal structure.+--+-- Laws:+--+-- > first unit . mult = idl+-- > second unit . mult = idr+-- > mult . first mult = mult . second mult . associate+--+class Monoidal k p => Multiplicative k p where+ unit :: k (Id k p) a+ mult :: k (p a a) a++-- | Comonoidal category with a comultiplication natural transformation.+--+-- A comultiplicative structure on @k@ is the same thing as a coalgebra object+-- structure on the identity functor, when End(k) is given the pointwise+-- comonoidal structure.+--+-- Laws:+--+-- > first counit . comult = coidl+-- > second counit . comult = coidr+-- > first diag . diag = disassociate . second diag . diag+--+class Monoidal k p => Comultiplicative k p where+ counit :: k a (Id k p)+ comult :: k a (p a a)
+ Control/Pipe.hs view
@@ -0,0 +1,450 @@+module Control.Pipe (+ -- * Tutorial+ --+ -- | This library provides a single data type: 'Pipe'.+ --+ -- '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+ -- enumeratees in other libraries because they receive an input stream and+ -- transform it into a new stream.+ --+ -- Let's introduce our first 'Pipe', which is a verbose version of the Prelude's+ -- 'take' function:+ --+ -- > take' :: Int -> Pipe a a IO ()+ -- > take' n = do+ -- > replicateM_ n $ do+ -- > x <- await+ -- > yield x+ -- > lift $ putStrLn "You shall not pass!"+ --+ -- This 'Pipe' allows the first @n@ values it receives to pass through+ -- undisturbed, then it outputs a cute message and shuts down. Shutdown is+ -- automatic when you reach the end of the monad. You don't need to send a+ -- special signal to connected 'Pipe's to let them know you are done handling+ -- input or generating output.+ --+ -- Let's dissect the above 'Pipe''s type to learn a bit about how 'Pipe's work:+ --+ -- > | Input Type | Output Type | Base monad | Return value+ -- > Pipe a a IO ()+ --+ -- So @take'@ 'await's input of type @a@ from upstream 'Pipe's and 'yield's+ -- output of type @a@ to downstream 'Pipe's. @take'@ uses 'IO' as its base+ -- monad because it invokes the 'putStrLn' function. If we remove the call to+ -- 'putStrLn' the compiler infers the following type instead, which is+ -- polymorphic in the base monad:+ --+ -- > 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. 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 () a m ()+ -- > fromList = mapM_ yield+ --+ -- We use @()@ as the input type of the 'Pipe' since it doesn't need 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'. We provide a type synonym for this common case:+ --+ -- > type Producer b m r = Pipe () b m r+ --+ -- You can then rewrite the type signature for @fromList@ as:+ --+ -- > fromList :: (Monad m) => [a] -> Producer a m ()+ --+ -- The compiler would be ok with a polymorphic input type, since without any+ -- calls to 'await' it doesn't need to constrain it. However, using @()@ makes+ -- it clear in the types that this 'Pipe' is designed to be used as a 'Producer',+ -- and statically prevents a number of mistakes when 'Pipe's are combined.+ --+ -- 'Producer's resemble enumerators in other libraries because they are a data+ -- source. It is not illegal to use 'await' in a 'Producer', it just returns+ -- @()@ immediately without blocking.+ --+ -- Now let's create a 'Pipe' that prints every value delivered to it and never+ -- terminates:+ --+ -- > printer :: (Show a) => Pipe a Void IO b+ -- > printer = forever $ do+ -- > x <- await+ -- > lift $ print x+ --+ -- The 'Void' in @printer@'s type signature indicates that it never delivers+ -- output downstream, so it represents the final stage in a 'Pipeline'. Again,+ -- we provide a type synonym for this common case:+ --+ -- > 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+ --+ -- 'Consumer's resemble iteratees in other libraries because they are a data+ -- sink. 'Consumer's never use 'yield' statements.+ --+ -- What makes 'Pipe's useful is the ability to compose them into 'Pipeline's.+ -- For that, we provide a '>+>' operator (and its right-to-left counterpart+ -- '<+<'):+ --+ -- > (>+>) :: Pipe a b m r -> Pipe b c m r -> Pipe a c m r+ -- > (<+<) :: Pipe b c m r -> Pipe a b m r -> Pipe a c m r+ --+ -- For example, here is how you can compose the above 'Pipe's:+ --+ -- > pipeline :: Pipe () Void IO ()+ -- > pipeline = fromList [1..] >+> take' 3 >+> printer+ --+ -- This represents a self-contained 'Pipeline' and we provide a type synonym+ -- for this common case:+ --+ -- > type Pipeline m r = Pipe () Void m r+ --+ -- Like many other monad transformers, you convert the 'Pipe' monad back to the+ -- base monad using some sort of \"@run...@\" function. In this case, we+ -- provide a 'runPipe' function:+ --+ -- > runPipe :: Pipeline IO r -> IO r+ --+ -- 'runPipe' is actually more general, since it works with any+ -- 'MonadBaseControl', but we will work with the above simplified signature in+ -- this tutorial.+ --+ -- There are also more general versione of 'runPipe' which work in+ -- any monad, but don't have any exception-safety guarantees, so they should+ -- only be used for 'Pipe's that don't allocate any scarce resources.+ --+ -- > runPipePipe :: (Monad m) => Pipeline m r -> m (Either SomeException r)+ -- > runPurePipe_ :: (Monad m) => Pipeline m r -> m r+ --+ -- 'runPipe', 'runPurePipe' and 'runPurePipe_' only work on self-contained+ -- 'Pipeline's. We explicitly require @()@ as input type and 'Void' as output+ -- type to ensure that the pipeline doesn't 'await' or 'yield' any value. If a+ -- 'Pipe' is polymorphic in its input type (for example because it never uses+ -- 'await'), then it can always be used as the first stage of a 'Pipeline'.+ -- Similarly, a 'Pipe' that is polymorphic in its output type can be used as+ -- the final stage.+ --+ -- It is generally good practice to use @()@ (resp. 'Void') explicitly as the+ -- input (resp. output) type of a producer (resp. consumer), since it gives the+ -- compiler more information on the intent of the 'Pipe', and makes some common+ -- errors detectable at compile time.+ --+ -- Let's try using 'runPipe':+ --+ -- >>> runPipe pipeline+ -- 1+ -- 2+ -- 3+ -- You shall not pass!+ --+ -- Fascinating! Our 'Pipe' terminated even though @printer@ never terminates+ -- and @fromList@ never terminates when given an infinite list. To illustrate+ -- why our 'Pipe' terminated, let's outline the flow control rules for 'Pipe'+ -- composition.+ --+ -- * Execution begins at the most downstream 'Pipe' (@printer@ in our example).+ --+ -- * If a 'Pipe' 'await's input, it blocks and transfers control to the next+ -- 'Pipe' upstream until that 'Pipe' 'yield's back a value.+ --+ -- * If a 'Pipe' 'yield's output, it restores control to the original+ -- downstream 'Pipe' that was 'await'ing its input and binds its result to+ -- the return value of the 'await' command.+ --+ -- * If a 'Pipe' terminates, it terminates every other 'Pipe' composed with it.+ --+ -- The last rule is crucial. If a 'Pipe' terminates then every downstream+ -- 'Pipe' depending on its output cannot proceed, and upstream 'Pipe's are+ -- never evaluated because the terminated 'Pipe' will not request values from+ -- them any longer.+ --+ -- So in our previous example, the 'Pipeline' terminated because @take' 3@+ -- terminated and brought down the entire 'Pipeline' with it.+ --+ -- 'Pipe's promote loose coupling, allowing you to mix and match them+ -- transparently using composition. For example, we can define a new+ -- 'Producer' pipe that indefinitely prompts the user for integers:+ --+ -- > prompt :: Producer Int IO a+ -- > prompt = forever $ do+ -- > lift $ putStrLn "Enter a number: "+ -- > n <- read <$> lift getLine+ -- > yield n+ --+ -- Now we can compose it with any of our previous 'Pipe's:+ --+ -- >>> runPipe $ prompt >+> take' 3 >+> printer+ -- Enter a number:+ -- 1<Enter>+ -- 1+ -- Enter a number:+ -- 2<Enter>+ -- 2+ -- Enter a number:+ -- 3<Enter>+ -- 3+ -- You shall not pass!+ --+ -- You can easily \"vertically\" concatenate 'Pipe's, 'Producer's, and+ -- 'Consumer's, all using simple monad sequencing: ('>>'). For example, here+ -- is how you concatenate 'Producer's:+ --+ -- >>> runPipe $ (fromList [1..3] >> fromList [10..12]) >+> printer+ -- 1+ -- 2+ -- 3+ -- 10+ -- 11+ -- 12+ --+ -- Here's how you would concatenate 'Consumer's:+ --+ -- > print' :: (Show a) => Int -> Consumer a IO ()+ -- > print' n = take' n >+> printer+ -- + -- >>> runPipe $ fromList [1..] >+> (print' 3 >> print' 4)+ -- 1+ -- 2+ -- 3+ -- You shall not pass!+ -- 4+ -- 5+ -- 6+ -- 7+ -- You shall not pass!+ --+ -- ... but the above example is gratuitous because we could have just+ -- concatenated the intermediate @take'@ 'Pipe':+ --+ -- >>> runPipe $ fromList [1..] >+> (take' 3 >> take' 4) >+> printer+ -- 1+ -- 2+ -- 3+ -- You shall not pass!+ -- 4+ -- 5+ -- 6+ -- 7+ -- You shall not pass!+ --+ -- Pipe composition imposes an important limitation: You can only compose+ -- 'Pipe's that have the same return type. For example, we could write the+ -- following function:+ --+ -- > deliver :: (Monad m) => Int -> Consumer a m [a]+ -- > deliver n = replicateM n await+ --+ -- ... and we might try to compose it with @fromList@:+ --+ -- >>> runPipe $ fromList [1..10] >+> deliver 3 -- wrong!+ --+ -- ... but this wouldn't type-check, because @fromList@ has a return type of+ -- @()@ and @deliver@ has a return type of @[Int]@. All 'Pipe's in a+ -- composition need to have the same return type, since the return value of the+ -- composed 'Pipe' is taken from the 'Pipe' that terminates first, and there's+ -- no general way to determine which one it is statically.+ --+ -- Fortunately, we don't have to rewrite the @fromList@ function because we can+ -- add a return value using vertical concatenation:+ --+ -- >>> runPipe $ (fromList [1..10] >> return []) >+> deliver 3+ -- [1,2,3]+ --+ -- ... although a more idiomatic Haskell version would be:+ --+ -- >>> runPipe $ (fromList [1..10] *> pure Nothing) >+> (Just <$> deliver 3)+ -- Just [1,2,3]+ --+ -- which can be written using the 'Control.Pipe.Combinators.$$' operator:+ --+ -- >>> runPipe $ fromList [1..10] $$ deliver 3+ -- Just [1,2,3]+ --+ -- This forces you to cover all code paths by thinking about what return value+ -- you would provide if something were to go wrong. For example, let's say I+ -- make a mistake and request more input than @fromList@ can deliver:+ --+ -- >>> runPipe $ fromList [1..10] $$ deliver 99+ -- Nothing+ --+ -- The type system saved me by forcing me to handle all possible ways my+ -- program could terminate.+ --+ -- Now what if you want to write a 'Pipe' that only reads from its input end+ -- (i.e. a 'Consumer') and returns a list of every value delivered to it when+ -- its input 'Pipe' terminates? In 'Control.Pipe.Combinators' we find:+ --+ -- > consume :: (Monad m) => Consumer a m [a]+ --+ -- but it turns out that it's not possible to write such a 'Pipe' using only+ -- the primitive introduced so far, since we need a way to intercept upstream+ -- termination and return the current accumulated list of input values before+ -- terminating ourselves.+ --+ -- So we need to introduce a new primitive operation:+ --+ -- > tryAwait :: (Monad m) => Pipe a b m (Maybe a)+ --+ -- 'tryAwait' works very similarly to 'await', with two key differences:+ --+ -- 1. When upstream 'yield's some value @x@, 'tryAwait' returns @Just x@.+ --+ -- 2. When upstream terminates, 'tryAwait' returns @Nothing@ instead of+ -- terminating the current 'Pipe' immediately.+ --+ -- When 'tryAwait' returns @Nothing@, the current 'Pipe' has a chance to+ -- perform some final actions (typically 'yield' a final value or terminate+ -- with a result) before being forcefully shut down. At that stage, further+ -- invocations of 'tryAwait' will keep returning @Nothing@, while using 'await'+ -- will terminate the pipe immediately.+ --+ -- Note that 'Pipe' termination only propagates through composition. To+ -- illustrate this, let's use the following example:+ --+ -- > p = do a <+< b+ -- > c+ --+ -- @a@, @b@, and @c@ are 'Pipe's, and @c@ shares the same input and output as+ -- @a <+< b@, otherwise we cannot combine them within the same monad. In the+ -- above example, either @a@ or @b@ could terminate and bring down the other+ -- one since they are composed, but @c@ is guaranteed to continue after+ -- @a <+< b@ terminates because it is not composed with them. Conceptually,+ -- we can think of this as @c@ automatically taking over the 'Pipe''s+ -- channeling responsibilities when @a <+< b@ can no longer continue. There+ -- is no need to \"restart\" the input or output manually as in some other+ -- iteratee libraries.+ --+ -- We now turn our attention to a very important feature of pipes: resource+ -- finalization.+ --+ -- Say we have the file \"test.txt\" with the following contents:+ --+ -- > This is a test.+ -- > Don't panic!+ -- > Calm down, please!+ --+ -- .. and we wish to lazily read a line at a time from it:+ --+ -- > handleReader' :: Handle -> Producer Text IO ()+ -- > handleReader' h = do+ -- > eof <- lift $ hIsEOF h+ -- > unless eof $ do+ -- > s <- lift $ pack <$> hGetLine h+ -- > yield s+ -- > handleReader' h+ --+ -- Suppose, for the sake of example, that we know in advance how many lines we+ -- need to read from the file. We can then use composition and the 'Monad'+ -- instance to try to build a resource-efficient version that only reads as+ -- many lines as we request:+ --+ -- > read' :: Int -> Producer Text IO ()+ -- > read' n = do+ -- > lift $ putStrLn "Opening file ..."+ -- > h <- lift $ openFile "test.txt" ReadMode+ -- > take' n <+< handleReader' h+ -- > lift $ putStrLn "Closing file ..."+ -- > lift $ hClose h+ --+ -- Now compose!+ --+ -- >>> runPipe $ read' 2 >+> printer+ -- Opening file ...+ -- "This is a test."+ -- "Don't panic!"+ -- You shall not pass!+ -- Closing file ...+ --+ -- >>> runPipe $ read' 99 >+> printer+ -- Opening file ...+ -- "This is a test."+ -- "Don't panic!"+ -- "Calm down, please!"+ -- Closing file ...+ --+ -- In the first example, the pipeline terminates because @take'@ only requested+ -- 2 lines. In the second example, it terminates because @readFile'@ ran out+ -- of input. However, in both cases the 'Pipe' never reads more lines than we+ -- request and frees \"test.txt\" immediately when it is no longer needed.+ --+ -- Even more importantly, the @file@ is never opened if we replace @printer@+ -- with a 'Pipe' that never demands input:+ --+ -- >>> runPipe $ read' 2 >+> lift (putStrLn "I don't need input")+ -- I don't need input+ --+ -- However, this @read'@ is not resource-safe in certain situations. For+ -- example, take the following pipe:+ --+ -- >>> runPipe $ read' 3 >+> take' 1 >+> printer+ -- Opening file ...+ -- "This is a test."+ -- You shall not pass!+ --+ -- Oh no! Our 'Pipe' didn't properly close our file! @take' 1@ terminated+ -- before @read' 3@, preventing @read' 3@ from properly closing \"test.txt\".+ --+ -- Similarly, any exception thrown during execution of the 'Pipeline' can cause+ -- the @hClose@ statement to be skipped, leaking an open handle.+ --+ -- We can force the @read' 3@ 'Pipe' to always close the file handle regardless+ -- of exceptions or premature termination by using the 'bracket' function:+ --+ -- > safeRead' :: Int -> Producer Text IO ()+ -- > safeRead' n = bracket+ -- > (putStrLn "Opening file..." >> openFile "test.txt" ReadMode)+ -- > (\h -> putStrLn "Closing file..." >> hClose h)+ -- > (\h -> handleReader' h >+> take' n)+ --+ -- 'bracket' is similar to the homonymous function in 'Control.Exception': it+ -- takes a function that creates some \"resource\", a function that disposes of+ -- the created resource, and a function which takes the resource and returns a+ -- 'Pipe':+ --+ -- > bracket :: Monad m+ -- > => m r -- create resource+ -- > -> (r -> m y) -- destroy resource+ -- > -> (r -> Pipe a b m x) -- use resource in a 'Pipe'+ -- > -> Pipe a b m x+ --+ -- Note that the \"create\" and \"destroy\" actions operate within the base+ -- monad, so it's not possible to use 'yield' and 'await' there.+ --+ -- Using @safeRead'@ instead of @read'@ will now produce the desired behavior:+ --+ -- >>> runPipe $ safeRead' 3 >+> take' 1 >+> printer+ -- Opening file...+ -- "This is a test."+ -- You shall not pass!+ -- Closing file...+ --+ -- We also provide exception-handling primitives like 'catch' and+ -- 'onException'. See 'Control.Pipe.Exception' for more details on exception+ -- handling and a complete list of primitives.+ --+ -- Resource finalization and exception handling functionalities work in any+ -- base monad, so we provide a 'Pipe'-specific mechanism for throwing+ -- exceptions which does not suffer from the limitation of only being catchable+ -- in the @IO@ monad:+ --+ -- > throw :: (Monad m, Exception e) => e -> Pipe a b m r+ --+ -- However, exceptions thrown by other means (like 'error' or @throw@ in+ -- 'Control.Exception'), can only be caught when the 'Pipeline' is run with+ -- 'runPipe'. If you use 'runPurePipe', such an exception will abruptly+ -- terminate the whole 'Pipeline', and resource finalization will not be+ -- guaranteed.++ -- * Implementation+ module Control.Pipe.Common,+ module Control.Pipe.Monoidal+ ) where++import Control.Pipe.Common+import Control.Pipe.Monoidal
+ Control/Pipe/Category.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE MultiParamTypeClasses, TypeFamilies #-}++module Control.Pipe.Category (+ -- | This module contains category-theoretic instances corresponding to basic+ -- pipe combinators in 'Control.Pipe.Common' and 'Control.Pipe.Monoidal'.+ PipeC(..),+ IFunctor(..),+ ) where++import Control.Categorical.Bifunctor+import Control.Category+import Control.Category.Associative+import Control.Category.Braided+import Control.Category.Monoidal+import Control.Category.Multiplicative+import Control.Monad+import Control.Pipe.Common+import Control.Pipe.Monoidal+import Data.Void+import Prelude hiding ((.), id)++-- | Category of pipes.+--+-- Composition corresponds to '<+<' and identity to 'idP'.+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 p2 . PipeC p1 = PipeC (p2 <+< p1)++-- | Identity-on-objects functor.+--+-- This is part of the interface of Arrow.+class Category k => IFunctor k where+ arr :: (a -> b) -> k a b++instance Monad m => IFunctor (PipeC m r) where+ arr = PipeC . pipe++instance Monad m => PFunctor Either (PipeC m r) (PipeC m r) where+ first = PipeC . firstP . unPipeC where++instance Monad m => QFunctor Either (PipeC m r) (PipeC m r) where+ second = PipeC . secondP . unPipeC where++instance Monad m => Bifunctor Either (PipeC m r) (PipeC m r) (PipeC m r) where+ bimap f g = first f >>> second g++instance Monad m => Associative (PipeC m r) Either where+ associate = PipeC associateP++instance Monad m => Disassociative (PipeC m r) Either where+ disassociate = PipeC disassociateP++type instance Id (PipeC m r) Either = Void++instance Monad m => Monoidal (PipeC m r) Either where+ idl = arr idl+ idr = arr idr++instance Monad m => Comonoidal (PipeC m r) Either where+ coidl = arr coidl+ coidr = arr coidr++instance Monad m => Braided (PipeC m r) Either where+ braid = arr braid++instance Monad m => Symmetric (PipeC m r) Either where++instance Monad m => Comultiplicative (PipeC m r) Either where+ counit = PipeC discard+ comult = PipeC splitP++instance Monad m => Multiplicative (PipeC m r) Either where+ unit = arr absurd+ mult = PipeC joinP
+ Control/Pipe/Combinators.hs view
@@ -0,0 +1,165 @@+{-# LANGUAGE ScopedTypeVariables #-}+-- | Basic pipe combinators.+module Control.Pipe.Combinators (+ -- ** Control operators+ tryAwait,+ forP,+ -- ** Composition+ ($$),+ -- ** Producers+ fromList,+ -- ** Folds+ -- | Folds are pipes that consume all their input and return a value. Some of+ -- them, like 'fold1', do not return anything when they don't receive any+ -- input at all. That means that the upstream return value will be returned+ -- instead.+ --+ -- Folds are normally used as 'Consumer's, but they are actually polymorphic+ -- in the output type, to encourage their use in the implementation of+ -- higher-level combinators.+ fold,+ fold1,+ consume,+ consume1,+ -- ** List-like pipe combinators+ -- The following combinators are analogous to the corresponding list+ -- functions, when the stream of input values is thought of as a (potentially+ -- infinite) list.+ take,+ drop,+ takeWhile,+ takeWhile_,+ dropWhile,+ intersperse,+ groupBy,+ filter,+ -- ** Other combinators+ pipeList,+ nullP,+ feed,+ ) where++import Control.Applicative+import Control.Monad+import Control.Pipe+import Control.Pipe.Exception+import Data.Maybe+import Prelude hiding (until, take, drop, concatMap, filter, takeWhile, dropWhile, catch)++-- | Like 'await', but returns @Just x@ when the upstream pipe yields some value+-- @x@, and 'Nothing' when it terminates.+--+-- Further calls to 'tryAwait' after upstream termination will keep returning+-- 'Nothing', whereas calling 'await' will terminate the current pipe+-- immediately.+tryAwait :: Monad m => Pipe a b m (Maybe a)+tryAwait = catch (Just <$> await) $ \(_ :: BrokenUpstreamPipe) -> return Nothing++-- | Execute the specified pipe for each value in the input stream.+--+-- Any action after a call to 'forP' will be executed when upstream terminates.+forP :: Monad m => (a -> Pipe a b m r) -> Pipe a b m ()+forP f = tryAwait >>= maybe (return ()) (\a -> f a >> forP f)++-- | Connect producer to consumer, ignoring producer return value.+infixr 5 $$+($$) :: Monad m => Pipe x a m r' -> Pipe a y m r -> Pipe x y m (Maybe r)+p1 $$ p2 = (p1 >> return Nothing) >+> fmap Just p2++-- | Successively yield elements of a list.+fromList :: Monad m => [a] -> Pipe x a m ()+fromList = mapM_ yield++-- | A pipe that terminates immediately.+nullP :: Monad m => Pipe a b m ()+nullP = return ()++-- | A fold pipe. Apply a binary function to successive input values and an+-- accumulator, and return the final result.+fold :: Monad m => (b -> a -> b) -> b -> Pipe a x m b+fold f = go+ where+ go x = tryAwait >>= maybe (return x) (go . f x)++-- | A variation of 'fold' without an initial value for the accumulator. This+-- pipe doesn't return any value if no input values are received.+fold1 :: Monad m => (a -> a -> a) -> Pipe a x m a+fold1 f = tryAwait >>= maybe discard (fold f)++-- | Accumulate all input values into a list.+consume :: Monad m => Pipe a x m [a]+consume = pipe (:) >+> (fold (.) id <*> pure [])++-- | Accumulate all input values into a non-empty list.+consume1 :: Monad m => Pipe a x m [a]+consume1 = pipe (:) >+> (fold1 (.) <*> pure [])++-- | Act as an identity for the first 'n' values, then terminate.+take :: Monad m => Int -> Pipe a a m ()+take n = replicateM_ n $ await >>= yield++-- | Remove the first 'n' values from the stream, then act as an identity.+drop :: Monad m => Int -> Pipe a a m r+drop n = replicateM_ n await >> idP++-- | Apply a function with multiple return values to the stream.+pipeList :: Monad m => (a -> [b]) -> Pipe a b m r+pipeList f = forever $ await >>= mapM_ yield . f++-- | Act as an identity until as long as inputs satisfy the given predicate.+-- Return the first element that doesn't satisfy the predicate.+takeWhile :: Monad m => (a -> Bool) -> Pipe a a m a+takeWhile p = go+ where+ go = await >>= \x -> if p x then yield x >> go else return x++-- | Variation of 'takeWhile' returning @()@.+takeWhile_ :: Monad m => (a -> Bool) -> Pipe a a m ()+takeWhile_ p = takeWhile p >> return ()++-- | Remove inputs as long as they satisfy the given predicate, then act as an+-- identity.+dropWhile :: Monad m => (a -> Bool) -> Pipe a a m r+dropWhile p = (takeWhile p >+> discard) >>= yield >> idP++-- | Yield Nothing when an input satisfying the predicate is received.+intersperse :: Monad m => (a -> Bool) -> Pipe a (Maybe a) m r+intersperse p = forever $ do+ x <- await+ when (p x) $ yield Nothing+ yield $ Just x++-- | Group input values by the given predicate.+groupBy :: Monad m => (a -> a -> Bool) -> Pipe a [a] m r+groupBy p = streaks >+> createGroups+ where+ streaks = await >>= \x -> yield (Just x) >> streaks' x+ streaks' x = do+ y <- await+ unless (p x y) $ yield Nothing+ yield $ Just y+ streaks' y+ createGroups = forever $+ takeWhile_ isJust >+>+ pipe fromJust >+>+ (consume1 >>= yield)++-- | Remove values from the stream that don't satisfy the given predicate.+filter :: Monad m => (a -> Bool) -> Pipe a a m r+filter p = forever $ takeWhile_ p++-- | Feed an input element to a pipe.+feed :: Monad m => a -> Pipe a b m r -> Pipe a b m r++-- this could be implemented as+-- feed x p = (yield x >> idP) >+> p+-- but this version is more efficient+feed _ (Pure r) = return r+feed _ (Throw e) = throw e+feed a (Free c h) = case go a c of+ (False, p) -> p >>= feed a+ (True, p) -> join p+ where+ go a (Await k) = (True, return $ k a)+ go _ (Yield y c) = (False, yield y >> return c)+ go _ (M m s) = (False, liftP s m)
+ Control/Pipe/Common.hs view
@@ -0,0 +1,330 @@+{-# LANGUAGE DeriveDataTypeable, FlexibleContexts #-}+module Control.Pipe.Common (+ -- ** Types+ Pipe(..),+ Producer,+ Consumer,+ Pipeline,+ Void,++ -- ** Primitives+ --+ -- | 'await' and 'yield' are the two basic primitives you need to create+ -- 'Pipe's. Because 'Pipe' is a monad, you can assemble them using ordinary+ -- @do@ notation. Since 'Pipe' is also a monad trnasformer, you can use+ -- 'lift' to invoke the base monad. For example:+ --+ -- > check :: Pipe a a IO r+ -- > check = forever $ do+ -- > x <- await+ -- > lift $ putStrLn $ "Can " ++ show x ++ " pass?"+ -- > ok <- lift $ read <$> getLine+ -- > when ok $ yield x+ await,+ yield,+ masked,++ -- ** Basic combinators+ pipe,+ idP,+ discard,+ (>+>),+ (<+<),++ -- ** Running pipes+ runPipe,+ runPurePipe,+ runPurePipe_,++ -- ** Low level types+ BrokenDownstreamPipe,+ BrokenUpstreamPipe,+ PipeF(..),+ MaskState(..),++ -- ** Low level primitives+ --+ -- | These functions can be used to implement exception-handling combinators.+ -- For normal use, prefer the functions defined in 'Control.Pipe.Exception'.+ throwP,+ catchP,+ liftP,+ ensure,+ ) where++import Control.Applicative+import Control.Category+import Control.Exception (SomeException, Exception)+import qualified Control.Exception.Lifted as E+import Control.Monad+import Control.Monad.Trans (MonadTrans, lift)+import Control.Monad.Trans.Control+import Data.Maybe+import Data.Typeable+import Data.Void+import Prelude hiding (id, (.), catch)++-- | The 'BrokenDownstreamPipe' exception is used to signal termination of the+-- downstream portion of a 'Pipeline' after the current pipe.+--+-- There is usually no need to catch this exception explicitly, a pipe will+-- terminate automatically when the downstream pipe terminates.+data BrokenDownstreamPipe = BrokenDownstreamPipe+ deriving (Show, Typeable)++instance Exception BrokenDownstreamPipe++-- | The 'BrokenUpstreamPipe' exception is used to signal termination of the+-- upstream portion of a 'Pipeline' before the current pipe+--+-- A 'BrokenUpstreamPipe' exception can be caught to perform cleanup actions+-- immediately before termination, like returning a result or yielding+-- additional values.+data BrokenUpstreamPipe = BrokenUpstreamPipe+ deriving (Show, Typeable)++instance Exception BrokenUpstreamPipe++-- | Type of action in the base monad.+data MaskState+ = Masked -- ^ Action to be run with asynchronous exceptions masked.+ | Unmasked -- ^ Action to be run with asynchronous exceptions unmasked.+ | Ensure -- ^ Action to be run regardless of downstream failure.+ | Finalizer SomeException -- ^ Finalizer action.++data PipeF a b m x+ = M (m x) MaskState+ | Await (a -> x)+ | Yield b x++instance Monad m => Functor (PipeF a b m) where+ fmap f (M m s) = M (liftM f m) s+ fmap f (Await k) = Await (f . k)+ fmap f (Yield b c) = Yield b (f c)++-- | The base type for pipes.+--+-- [@a@] The type of input received fom upstream pipes.+--+-- [@b@] The type of output delivered to downstream pipes.+--+-- [@c@] The base monad.+--+-- [@d@] The type of the monad's final result.+data Pipe a b m r+ -- Pipe is a free monad over the functor+ --+ -- data PipeF' a b m r+ -- = Catch (PipeF a b m r) (SomeException -> r)+ -- | Throw e+ -- + -- but is implemented inline because it makes the code simpler.+ = Pure r+ | Free (PipeF a b m (Pipe a b m r))+ (SomeException -> Pipe a b m r)+ | Throw SomeException++-- | A pipe that can only produce values.+type Producer b m = Pipe () b m++-- | A pipe that can only consume values.+type Consumer a m = Pipe a Void m++-- | A self-contained pipeline that is ready to be run.+type Pipeline m = Pipe () Void m++instance Monad m => Monad (Pipe a b m) where+ return = Pure+ Pure r >>= f = f r+ Free c h >>= f = Free (fmap (>>= f) c)+ (h >=> f)+ Throw e >>= _ = Throw e++instance Monad m => Functor (Pipe a b m) where+ fmap = liftM++instance Monad m => Applicative (Pipe a b m) where+ pure = return+ (<*>) = ap++liftF :: Monad m => PipeF a b m r -> Pipe a b m r+liftF c = Free (fmap return c) throwP++-- | Throw an exception within the 'Pipe' monad.+throwP :: Monad m => SomeException -> Pipe a b m r+throwP = Throw++-- | Catch an exception within the pipe monad.+catchP :: Monad m+ => Pipe a b m r+ -> (SomeException -> Pipe a b m r)+ -> Pipe a b m r+catchP (Pure r) _ = return r+catchP (Free c h1) h2 = Free+ (fmap (`catchP` h2) c)+ (\e -> catchP (h1 e) h2)+catchP (Throw e) h = h e++-- | Wait for input from upstream within the 'Pipe' monad.+--+-- 'await' blocks until input is ready.+await :: Monad m => Pipe a b m a+await = liftF $ Await id++-- | Pass output downstream within the 'Pipe' monad.+--+-- 'yield' blocks until the downstream pipe calls 'await' again.+yield :: Monad m => b -> Pipe a b m ()+yield x = liftF $ Yield x ()++-- | Execute an action in the base monad with the given 'MaskState'.+liftP :: Monad m => MaskState -> m r -> Pipe a b m r+liftP s m = liftF (M m s)++instance MonadTrans (Pipe a b) where+ lift = liftP Unmasked++-- | Execute an action in the base monad with asynchronous exceptions masked.+--+-- This function is effective only if the 'Pipeline' is run with 'runPipe',+-- otherwise it is identical to 'lift'+masked :: Monad m => m r -> Pipe a b m r+masked = liftP Masked++-- | Ensure an action is executed regardless of downstream termination.+ensure :: Monad m => m r -> Pipe a b m r+ensure = liftP Ensure++finalizer :: Monad m => SomeException -> m r -> Pipe a b m r+finalizer e = liftP (Finalizer e)++-- | Convert a pure function into a pipe.+--+-- > pipe = forever $ do+-- > x <- await+-- > yield (f x)+pipe :: Monad m => (a -> b) -> Pipe a b m r+pipe f = forever $ await >>= yield . f++-- | The identity pipe.+idP :: Monad m => Pipe a a m r+idP = pipe id++-- | The 'discard' pipe silently discards all input fed to it.+discard :: Monad m => Pipe a b m r+discard = forever await++data Composition a b c m x y+ = AdvanceFirst (Pipe a c m x)+ | AdvanceSecond (Pipe a c m y)+ | AdvanceBoth x y++compose :: Monad m+ => PipeF a b m x+ -> PipeF b c m y+ -> Maybe (Composition a b c m x y)+compose (Yield b x) (Await k) = Just $ AdvanceBoth x (k b)+compose _ (Yield c y) = Just $ AdvanceSecond (yield c >> return y)+compose _ (M m s) = Just $ AdvanceSecond (liftP s m)+compose (M _ (Finalizer _)) _ = Nothing+compose (M m s) _ = Just $ AdvanceFirst (liftP s m)+compose (Await k) _ = Just $ AdvanceFirst (liftM k await)++finalize2 :: Monad m+ => PipeF b c m r+ -> Maybe (Pipe a c m r)+finalize2 (Await _) = Nothing+finalize2 (M m s) = Just $ liftP s m+finalize2 (Yield c r) = Just $ yield c >> return r++finalize1 :: Monad m+ => Maybe SomeException+ -> PipeF a b m r+ -> Maybe (Pipe a c m r)+finalize1 e c = case c of+ M m Ensure -> go m+ M m (Finalizer _) -> go m+ _ -> Nothing+ where+ go m = Just $+ finalizer (fromMaybe (E.toException BrokenUpstreamPipe) e) m++infixl 9 >+>+-- | Left to right pipe composition.+(>+>) :: Monad m => Pipe a b m r -> Pipe b c m r -> Pipe a c m r+p1 >+> p2 = case (p1, p2) of+ (Free c1 h1, Free c2 h2) -> case compose c1 c2 of+ Nothing -> p1 >+> h2 (E.toException BrokenUpstreamPipe)+ Just (AdvanceFirst comp) -> catchP comp (return . h1) >>= \p1' -> p1' >+> p2+ Just (AdvanceSecond comp) -> catchP comp (return . h2) >>= \p2' -> p1 >+> p2'+ Just (AdvanceBoth p1' p2') -> p1' >+> p2'+ (Throw e, Free c h) -> terminate2 c h (Just e)+ (Pure r, Free c h) -> terminate2 c h Nothing+ (Free c h, Throw e) -> terminate1 c h (Just e)+ (Free c h, Pure r) -> terminate1 c h Nothing+ (Pure r, Throw e) -> case (E.fromException e :: Maybe BrokenUpstreamPipe) of+ Nothing -> throwP e+ Just _ -> return r+ (_, Throw e) -> throwP e+ (_, Pure r) -> return r+ where+ terminate1 c h e = case finalize1 e c of+ Nothing -> h (fromMaybe (E.toException BrokenDownstreamPipe) e) >+> p2+ Just comp -> catchP comp (return . h) >>= \p1' -> p1' >+> p2+ terminate2 c h e = case finalize2 c of+ Nothing -> p1 >+> h (fromMaybe (E.toException BrokenUpstreamPipe) e)+ Just comp -> catchP comp (return . h) >>= \p2' -> p1 >+> p2'++infixr 9 <+<+-- | Right to left pipe composition.+(<+<) :: Monad m => Pipe b c m r -> Pipe a b m r -> Pipe a c m r+p2 <+< p1 = p1 >+> p2++-- | Run a self-contained 'Pipeline', converting it to an action in the base+-- monad.+--+-- This function is exception-safe. Any exception thrown in the base monad+-- during execution of the pipeline will be captured by+-- 'Control.Pipe.Exception.catch' statements in the 'Pipe' monad.+runPipe :: MonadBaseControl IO m => Pipeline m r -> m r+runPipe p = E.mask $ \restore -> run restore p+ where+ run restore = go+ where+ go (Pure r) = return r+ go (Free c h) = stepPipe try c >>= \x -> case x of+ Left e -> go $ h e+ Right p' -> go p'+ go (Throw e) = E.throwIO e++ try m s = E.try $ case s of+ Unmasked -> restore m+ _ -> m++-- | Run a self-contained pipeline over an arbitrary monad, with fewer+-- exception-safety guarantees than 'runPipe'.+--+-- Only pipe termination exceptions and exceptions thrown using+-- 'Control.Pipe.Exception.throw' will be catchable within the 'Pipe' monad.+-- Any other exception will terminate execution immediately and finalizers will+-- not be called.+--+-- Any captured exception will be returned in the left component of the result.+runPurePipe :: Monad m => Pipeline m r -> m (Either SomeException r)+runPurePipe (Pure r) = return $ Right r+runPurePipe (Throw e) = return $ Left e+runPurePipe (Free c h) = stepPipe try c >>= runPurePipe . either h id+ where try m _ = liftM Right m++-- | A version of 'runPurePipe' which rethrows any captured exception instead+-- of returning it.+runPurePipe_ :: Monad m => Pipeline m r -> m r+runPurePipe_ = runPurePipe >=> either E.throw return++stepPipe :: Monad m+ => (m r -> MaskState -> m (Either SomeException r))+ -> PipeF () Void m r+ -> m (Either SomeException r)+stepPipe _ (Await k) = return . Right $ k ()+stepPipe _ (Yield x _) = absurd x+stepPipe try (M m s) = try m s
+ Control/Pipe/Exception.hs view
@@ -0,0 +1,116 @@+module Control.Pipe.Exception (+ throw,+ catch,+ bracket,+ bracket_,+ bracketOnError,+ finally,+ onException,+ ) where++import qualified Control.Exception as E+import Control.Pipe.Common+import Prelude hiding (catch)++-- | Catch an exception within the 'Pipe' monad.+--+-- This function takes a 'Pipe', runs it, and if an exception is raised it+-- executes the handler, passing it the value of the exception. Otherwise, the+-- result is returned as normal.+--+-- For example, given a 'Pipe':+--+-- > reader :: Pipe () String IO ()+--+-- we can use 'catch' to resume after an exception. For example:+--+-- > safeReader :: Pipe () (Either SomeException String) IO ()+-- > safeReader = catch (reader >+> 'Pipe' Right) $ \e -> do+-- > yield $ Left e+--+-- Note that there is no guarantee that the handler will actually be executed,+-- as any action in a 'Pipe': 'Pipe's at either side can terminate before the+-- handler has a chance to be executed.+--+-- It is therefore common to use 'ensure' within an exception handler to+-- perform cleanup or finalization of resources. However, we recommend using+-- 'finally' or 'bracket' for such use cases.+catch :: (Monad m, E.Exception e)+ => Pipe a b m r -- ^ 'Pipe' to run+ -> (e -> Pipe a b m r) -- ^ handler function+ -> Pipe a b m r+catch p h = catchP p $ \e -> case E.fromException e of+ Nothing -> throwP e+ Just e' -> h e'++-- | Throw an exception within the 'Pipe' monad.+--+-- An exception thrown with 'throw' can be caught by 'catch' with any base+-- monad.+--+-- If the exception is not caught in the 'Pipeline' at all, it will be rethrown+-- as a normal Haskell exception when using 'runPipe'. Note that 'runPurePipe'+-- returns the exception in an 'Either' value, instead.+throw :: (Monad m, E.Exception e) => e -> Pipe a b m r+throw = throwP . E.toException++-- | Like 'finally', but only performs the final action if there was an+-- exception raised by the 'Pipe'.+onException :: Monad m+ => Pipe a b m r -- ^ 'Pipe' to run first+ -> Pipe a b m s -- ^ 'Pipe' to run if an exception happens+ -> Pipe a b m r+onException p w = catchP p $ \e -> w >> throw e++-- | A specialized variant of 'bracket' with just a computation to run+-- afterwards.+finally :: Monad m+ => Pipe a b m r -- ^ 'Pipe' to run first+ -> m s -- ^ finalizer action+ -> Pipe a b m r+finally p w = do+ r <- onException p (ensure w)+ ensure w+ return r++-- | Allocate a resource within the base monad, run a 'Pipe', then ensure the+-- resource is released.+--+-- The typical example is reading from a file:+--+-- > bracket+-- > (openFile "filename" ReadMode)+-- > hClose+-- > (\handle -> do+-- > line <- lift $ hGetLine handle+-- > yield line+-- > ...)+bracket :: Monad m+ => m r -- ^ action to acquire resource+ -> (r -> m y) -- ^ action to release resource+ -> (r -> Pipe a b m x) -- ^ 'Pipe' to run in between+ -> Pipe a b m x+bracket open close run = do+ r <- liftP Masked open+ finally (run r) (close r)++-- | A variant of 'bracket' where the return value from the allocation action+-- is not required.+bracket_ :: Monad m+ => m r -- ^ action to run first+ -> m y -- ^ action to run last+ -> Pipe a b m x -- ^ 'Pipe' to run in between+ -> Pipe a b m x+bracket_ open close run =+ bracket open (const close) (const run)++-- | Like 'bracket', but only performs the \"release\" action if there was an+-- exception raised by the 'Pipe'.+bracketOnError :: Monad m+ => m r -- ^ action to acquire resource+ -> (r -> m y) -- ^ action to release resource+ -> (r -> Pipe a b m x) -- ^ 'Pipe' to run in between+ -> Pipe a b m x+bracketOnError open close run = do+ r <- liftP Masked open+ onException (run r) (ensure $ close r)
+ Control/Pipe/Monoidal.hs view
@@ -0,0 +1,128 @@+module Control.Pipe.Monoidal (+ -- | The combinators in this module allow you to create and manipulate+ -- multi-channel pipes. Multiple input or output channels are represented with+ -- 'Either' types.+ --+ -- Most of the combinators are generalizations of the corresponding functions+ -- in 'Control.Arrow', and obey appropriately generalized laws.+ firstP,+ secondP,+ (***),+ associateP,+ disassociateP,+ discardL,+ discardR,+ swapP,+ joinP,+ splitP,+ loopP,+ ) where++import Control.Category.Associative+import Control.Category.Braided+import Control.Category.Monoidal+import Control.Monad+import qualified Control.Monad.Trans as T+import Control.Monad.State+import Control.Pipe.Common++-- | Create a 'Pipe' that behaves like the given 'Pipe' of the left component+-- of the input, and lets values in the right component pass through.+firstP :: Monad m+ => Pipe a b m r+ -> Pipe (Either a c) (Either b c) m r+firstP (Pure r) = return r+firstP (Throw e) = Throw e+firstP (Free c h) = catchP (step c) (return . h) >>= firstP+ where+ step (M m s) = liftP s m+ step (Yield b x) = yield (Left b) >> return x+ step (Await k) = go+ where+ go = await >>= either (return . k)+ (yield . Right >=> const go)++-- | This function is the equivalent of 'firstP' for the right component.+secondP :: Monad m+ => Pipe a b m r+ -> Pipe (Either c a) (Either c b) m r+secondP (Pure r) = return r+secondP (Throw e) = Throw e+secondP (Free c h) = catchP (step c) (return . h) >>= secondP+ where+ step (M m s) = liftP s m+ step (Yield b x) = yield (Right b) >> return x+ step (Await k) = go+ where+ go = await >>= either (yield . Left >=> const go)+ (return . k)++-- | Combine two pipes into a single pipe that behaves like the first on the+-- left component, and the second on the right component.+(***) :: Monad m+ => Pipe a b m r+ -> Pipe a' b' m r+ -> Pipe (Either a a') (Either b b') m r+p1 *** p2 = firstP p1 >+> secondP p2++-- | Convert between the two possible associations of a triple sum.+associateP :: Monad m+ => Pipe (Either (Either a b) c) (Either a (Either b c)) m r+associateP = pipe associate++-- | Inverse of 'associateP'.+disassociateP :: Monad m+ => Pipe (Either a (Either b c)) (Either (Either a b) c) m r+disassociateP = pipe disassociate++-- | Discard all values on the left component.+discardL :: Monad m => Pipe (Either x a) a m r+discardL = firstP discard >+> pipe idl++-- | Discard all values on the right component.+discardR :: Monad m => Pipe (Either a x) a m r+discardR = secondP discard >+> pipe idr++-- | Swap the left and right components.+swapP :: Monad m => Pipe (Either a b) (Either b a) m r+swapP = pipe swap++-- | Yield all input values into both the left and right components of the+-- output.+splitP :: Monad m => Pipe a (Either a a) m r+splitP = forever $ await >>= yield2+ where+ yield2 x = yield (Left x) >> yield (Right x)++-- | Yield both components of input values into the output.+joinP :: Monad m => Pipe (Either a a) a m r+joinP = pipe $ either id id++data Queue a = Queue ![a] ![a]++emptyQueue :: Queue a+emptyQueue = Queue [] []++enqueue :: a -> Queue a -> Queue a+enqueue x (Queue xs ys) = Queue (x : xs) ys++dequeue :: Queue a -> (Queue a, Maybe a)+dequeue (Queue (x : xs) ys) = (Queue xs ys, Just x)+dequeue q@(Queue [] []) = (q, Nothing)+dequeue (Queue [] ys) = dequeue (Queue (reverse ys) [])++-- | The 'loopP' combinator allows to create 'Pipe's whose output value is fed+-- back to the 'Pipe' as input.+loopP :: Monad m => Pipe (Either a c) (Either b c) m r -> Pipe a b m r+loopP = go emptyQueue+ where+ go _ (Pure r) = return r+ go _ (Throw e) = throwP e+ go q (Free c h) = case step q c of+ (q', p') -> catchP p' (return . h) >>= go q'++ step q (Await k) = case dequeue q of+ (q', x) -> (q', maybe (liftM (k . Left) await) (return . k . Right) x)+ step q (Yield (Right x) c) = (enqueue x q, return c)+ step q (Yield (Left x) c) = (q, yield x >> return c)+ step q (M m s) = (q, liftP s m)
+ LICENSE view
@@ -0,0 +1,25 @@+Copyright (c) 2012, Gabriel Gonzalez+Copyright (c) 2012, Paolo Capriotti+All rights reserved.++Redistribution and use in source and binary forms, with or without modification,+are permitted provided that the following conditions are met:+ * Redistributions of source code must retain the above copyright notice,+ this list of conditions and the following disclaimer.+ * Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the documentation+ and/or other materials provided with the distribution.+ * Neither the name of Gabriel Gonzalez nor the names of other contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ pipes-core.cabal view
@@ -0,0 +1,65 @@+Name: pipes-core+Version: 0.0.1+Cabal-Version: >=1.10.1+Build-Type: Simple+License: BSD3+License-File: LICENSE+Copyright: 2012 Gabriel Gonzalez, 2012 Paolo Capriotti+Author: Gabriel Gonzalez, Paolo Capriotti+Maintainer: p.capriotti@gmail.com+Stability: Experimental+Homepage: https://github.com/pcapriotti/pipes-core+Bug-Reports: https://github.com/pcapriotti/pipes-core/issues+Synopsis: Compositional pipelines+Description:+ This library offers an abstraction similar in scope to+ iteratees\/enumerators\/enumeratees, but with different characteristics and+ naming conventions.+ .+ Difference with traditional iteratees:+ .+ * /Simpler semantics/: There is only one data type ('Pipe'), two primitives+ ('await' and 'yield'), and only one way to compose 'Pipe's ('>+>'). In+ fact, ('>+>') is just convenient syntax for the composition operator in+ 'Category'. Most pipes can be implemented just using the 'Monad' instance+ and composition.+ .+ * /Different 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.+ .+ * /Pipes form a Category/: that means that composition is associative, and+ that there is an identity 'Pipe'.+ .+ * /"Vertical" concatenation works on every 'Pipe'/: ('>>'),+ concatenates 'Pipe's. Since everything is a 'Pipe', you can use it to+ concatenate 'Producer's, 'Consumer's, and even intermediate 'Pipe' stages.+ Vertical concatenation can be combined with composition to create elaborate+ combinators, without the need of executing pipes in \"passes\" or resuming+ partially executed pipes.+ .+ Check out "Control.Pipe" for a copious introduction (in the spirit of the+ @iterIO@ library), and "Control.Pipe.Combinators" for some basic combinators+ and 'Pipe' examples.+Category: Control, Enumerator+Tested-With: GHC ==7.0.3+Source-Repository head+ Type: git+ Location: https://github.com/pcapriotti/pipes-core++Library+ Build-Depends: base >= 4 && < 5+ , mtl+ , categories+ , void+ , lifted-base+ , monad-control+ Exposed-Modules: Control.Pipe+ , Control.Pipe.Category+ , Control.Pipe.Combinators+ , Control.Pipe.Common+ , Control.Pipe.Exception+ , Control.Pipe.Monoidal+ , Control.Category.Multiplicative+ Default-Language: Haskell2010