diff --git a/Control/Category/Multiplicative.hs b/Control/Category/Multiplicative.hs
new file mode 100644
--- /dev/null
+++ b/Control/Category/Multiplicative.hs
@@ -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)
diff --git a/Control/Pipe.hs b/Control/Pipe.hs
new file mode 100644
--- /dev/null
+++ b/Control/Pipe.hs
@@ -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
diff --git a/Control/Pipe/Category.hs b/Control/Pipe/Category.hs
new file mode 100644
--- /dev/null
+++ b/Control/Pipe/Category.hs
@@ -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
diff --git a/Control/Pipe/Combinators.hs b/Control/Pipe/Combinators.hs
new file mode 100644
--- /dev/null
+++ b/Control/Pipe/Combinators.hs
@@ -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)
diff --git a/Control/Pipe/Common.hs b/Control/Pipe/Common.hs
new file mode 100644
--- /dev/null
+++ b/Control/Pipe/Common.hs
@@ -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
diff --git a/Control/Pipe/Exception.hs b/Control/Pipe/Exception.hs
new file mode 100644
--- /dev/null
+++ b/Control/Pipe/Exception.hs
@@ -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)
diff --git a/Control/Pipe/Monoidal.hs b/Control/Pipe/Monoidal.hs
new file mode 100644
--- /dev/null
+++ b/Control/Pipe/Monoidal.hs
@@ -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)
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/pipes-core.cabal b/pipes-core.cabal
new file mode 100644
--- /dev/null
+++ b/pipes-core.cabal
@@ -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
