diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,26 @@
 Changelog
 =========
 
+Version 0.2.1.0
+---------------
+
+*October 30, 2019*
+
+<https://github.com/mstksg/conduino/releases/tag/v0.2.1.0>
+
+*   `hoistPipe` exported from *Data.Conduit*
+*   A handful of pipe primitive combinators added to *Data.Conduit*, including:
+    *   `feedbackPipe`
+    *   `zipPipe`
+    *   `&|` / `fuseBoth`
+    *   `|.` / `fuseUpstream`
+    *   `fuseBothMaybe`
+*   Some pipe runners added to *Data.Conduit*, including:
+    *   `squeezePipe` / `squeezePipeEither`
+    *   `feedPipe` / `feedPipeEither`
+*   *Data.Conduit.Lift* module, for working with monad transformers
+*   `iterM` added to *Data.Conduit.Combinators*
+
 Version 0.2.0.0
 ---------------
 
diff --git a/conduino.cabal b/conduino.cabal
--- a/conduino.cabal
+++ b/conduino.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: e4f50c4a474c128ace934290082d6a4eda455370d44bb3efb0192215a4fbcd1b
+-- hash: 52a1f0db85b8a281a18d41d489266853a6510e8e6ea76c959b68bf3cbb5ac0ce
 
 name:           conduino
-version:        0.2.0.0
+version:        0.2.1.0
 synopsis:       Lightweight composable continuation-based stream processors
 description:    A lightweight continuation-based stream processing library.
                 .
@@ -38,6 +38,7 @@
       Data.Conduino
       Data.Conduino.Combinators
       Data.Conduino.Internal
+      Data.Conduino.Lift
   other-modules:
       Paths_conduino
   hs-source-dirs:
@@ -47,6 +48,7 @@
       base >=4.11 && <5
     , bytestring
     , containers
+    , exceptions
     , free
     , list-transformer
     , mtl
diff --git a/src/Data/Conduino.hs b/src/Data/Conduino.hs
--- a/src/Data/Conduino.hs
+++ b/src/Data/Conduino.hs
@@ -5,6 +5,7 @@
 {-# LANGUAGE PatternSynonyms            #-}
 {-# LANGUAGE RankNTypes                 #-}
 {-# LANGUAGE ScopedTypeVariables        #-}
+{-# LANGUAGE TupleSections              #-}
 {-# LANGUAGE TypeInType                 #-}
 {-# LANGUAGE ViewPatterns               #-}
 
@@ -23,36 +24,36 @@
 -- A "prelude" of useful pipes can be found in "Data.Conduino.Combinators".
 --
 -- == Why a stream processing library?
--- 
+--
 -- A stream processing library is a way to stream processors in a /composable/ way:
 -- instead of defining your entire stream processing function as a single
 -- recursive loop with some global state, instead think about each "stage" of the process,
 -- and isolate each state to its own segment.  Each component can contain its own
 -- isolated state:
--- 
+--
 -- >>> runPipePure $ sourceList [1..10]
 --       .| scan (+) 0
 --       .| sinkList
 -- [1,3,6,10,15,21,28,36,45,55]
--- 
+--
 -- All of these components have internal "state":
--- 
+--
 -- *   @sourceList@ keeps track of "which" item in the list to yield next
 -- *   @scan@ keeps track of the current running sum
 -- *   @sinkList@ keeps track of all items that have been seen so far, as a list
--- 
+--
 -- They all work together without knowing any other component's internal state, so
 -- you can write your total streaming function without concerning yourself, at
 -- each stage, with the entire part.
--- 
+--
 -- In addition, there are useful functions to "combine" stream processors:
--- 
+--
 -- *   'zipSink' combines sinks in an "and" sort of way: combine two sinks in
 --     parallel and finish when all finish.
 -- *   'altSink' combines sinks in an "or" sort of way: combine two sinks in
 --     parallel and finish when any of them finish
 -- *   'zipSource' combines sources in parallel and collate their outputs.
--- 
+--
 -- Stream processing libraries are also useful for streaming composition of
 -- monadic effects (like IO or State), as well.
 --
@@ -60,11 +61,22 @@
     Pipe
   , (.|)
   , runPipe, runPipePure
+  -- * Primitives
   , awaitEither, await, awaitWith, awaitSurely, awaitForever, yield
+  -- * Special chaining
+  , (&|), (|.)
+  , fuseBoth, fuseUpstream, fuseBothMaybe
+  -- * Incremental running
+  , squeezePipe, squeezePipeEither
+  , feedPipe, feedPipeEither
+  -- * Pipe transformers
   , mapInput, mapOutput, mapUpRes, trimapPipe
+  , hoistPipe
+  , feedbackPipe
   -- * Wrappers
   , ZipSource(..)
   , unconsZipSource
+  , zipSource
   , ZipSink(..)
   , zipSink, altSink
   -- * Generators
@@ -78,11 +90,15 @@
 import           Control.Monad.Trans.Class
 import           Control.Monad.Trans.Free        (FreeT(..), FreeF(..))
 import           Control.Monad.Trans.Free.Church
+import           Control.Monad.Trans.State
+import           Data.Bifunctor
 import           Data.Conduino.Internal
 import           Data.Functor
 import           Data.Functor.Identity
+import           Data.Sequence                   (Seq(..))
 import           Data.Void
 import           List.Transformer                (ListT(..), Step(..))
+import qualified Data.Sequence                   as Seq
 import qualified List.Transformer                as LT
 
 -- | Await input from upstream.  Will block until upstream 'yield's.
@@ -195,6 +211,77 @@
 runPipePure :: Pipe () Void Void Identity a -> a
 runPipePure = runIdentity . runPipe
 
+-- | Repeatedly run 'squeezePipe' by giving it items from an input list.
+-- Returns the outputs observed, and 'Left' if the input list was exhausted
+-- with more input expected, or 'Right' if the pipe terminated, with the
+-- leftover inputs and output result.
+--
+-- @since 0.2.1.0
+feedPipe
+    :: Monad m
+    => [i]
+    -> Pipe i o u m a
+    -> m ([o], Either (i -> Pipe i o u m a) ([i], a))
+feedPipe xs = (fmap . second . first) (. Right)
+            . feedPipeEither xs
+
+-- | Repeatedly run 'squeezePipeEither' by giving it items from an input
+-- list.  Returns the outputs observed, and 'Left' if the input list was
+-- exhausted with more input expected (or a @u@ terminating upstream
+-- value), or 'Right' if the pipe terminated, with the leftover inputs and
+-- output result.
+--
+-- @since 0.2.1.0
+feedPipeEither
+    :: Monad m
+    => [i]
+    -> Pipe i o u m a
+    -> m ([o], Either (Either u i -> Pipe i o u m a) ([i], a))
+feedPipeEither xs p = do
+    (zs, r) <- squeezePipeEither p
+    case r of
+      Left n -> case xs of
+        []   -> pure (zs, Left n)
+        y:ys -> first (zs ++) <$> feedPipeEither ys (n (Right y))
+      Right z -> pure (zs, Right (xs, z))
+
+-- | "Squeeze" a pipe by extracting all output that can be extracted
+-- before any input is requested.  Returns a 'Left' if the pipe eventually
+-- does request input (as a continuation on the new input), or a 'Right' if
+-- the pipe terminates with a value before ever asking for input.
+--
+-- @since 0.2.1.0
+squeezePipe
+    :: Monad m
+    => Pipe i o u m a
+    -> m ([o], Either (i -> Pipe i o u m a) a)
+squeezePipe = (fmap . second . first) (. Right)
+            . squeezePipeEither
+
+-- | "Squeeze" a pipe by extracting all output that can be extracted before
+-- any input is requested.  Returns a 'Left' if the pipe eventually does
+-- request input (as a continuation on the new input, or a terminating @u@
+-- value), or a 'Right' if the pipe terminates with a value before ever
+-- asking for input.
+--
+-- @since 0.2.1.0
+squeezePipeEither
+    :: Monad m
+    => Pipe i o u m a
+    -> m ([o], Either (Either u i -> Pipe i o u m a) a)
+squeezePipeEither p = runFT (pipeFree p)
+    (pure . ([],) . Right)
+    (\pNext -> \case
+        PAwaitF f g -> pure . ([],) . Left $ (unSqueeze =<<) . lift . pNext . either f g
+        PYieldF o x -> first (o:) <$> pNext x
+    )
+  where
+    unSqueeze (os, nxt) = do
+      mapM_ yield os
+      case nxt of
+        Left f  -> f =<< awaitEither
+        Right a -> pure a
+
 -- | The main operator for chaining pipes together.  @pipe1 .| pipe2@ will
 -- connect the output of @pipe1@ to the input of @pipe2@.
 --
@@ -235,6 +322,90 @@
       Free (PYieldF x' y') -> runFreeT $ compPipe_ y' (g x')
     Free (PYieldF x y) -> pure . Free $ PYieldF x (compPipe_ p y)
 
+-- | Useful prefix version of '&|'.
+--
+-- @since 0.2.1.0
+fuseBoth
+    :: Monad m
+    => Pipe a b u m v
+    -> Pipe b c v m r
+    -> Pipe a c u m (v, r)
+fuseBoth p q = p
+            .| (q >>= exhaust)
+  where
+    exhaust x = go
+      where
+        go = awaitEither >>= \case
+          Left  y -> pure (y, x)
+          Right _ -> go
+
+-- | Like 'fuseBoth' and '&|', except does not wait for the upstream pipe
+-- to terminate.  Return 'Nothing' in the first field if the upstream pipe hasn't terminated,
+-- and 'Just' if it has, with the terminating value.
+--
+-- @since 0.2.1.0
+fuseBothMaybe :: Monad m => Pipe a b u m v -> Pipe b c v m r -> Pipe a c u m (Maybe v, r)
+fuseBothMaybe p q = p
+                 .| (q >>= check)
+  where
+    check x = (,x) . either Just (const Nothing) <$> awaitEither
+
+-- | Useful prefix version of '|.'.
+--
+-- @since 0.2.1.0
+fuseUpstream
+    :: Monad m
+    => Pipe a b u m v
+    -> Pipe b c v m r
+    -> Pipe a c u m v
+fuseUpstream p q = fst <$> fuseBoth p q
+
+-- | Like @.|@, but get the result of /both/ pipes on termination, instead
+-- of just the second.  This means that @p &| q@ will only terminate with a result when
+-- /both/ @p@ and @q@ terminate.  (Typically, @p .| q@ would terminate as soon as
+-- @q@ terminates.)
+--
+-- @since 0.2.1.0
+(&|) :: Monad m => Pipe a b u m v -> Pipe b c v m r -> Pipe a c u m (v, r)
+(&|) = fuseBoth
+
+-- | Like @.|@, but keep the result of the /first/ pipe, instead of the
+-- second.  This means that @p |. q@ will only terminate with a result when
+-- /both/ @p@ and @q@ terminate.  (Typically, @p .| q@ would terminate as soon as
+-- @q@ terminates.)
+--
+-- @since 0.2.1.0
+(|.) :: Monad m => Pipe a b u m v -> Pipe b c v m r -> Pipe a c u m v
+(|.) = fuseUpstream
+
+infixr 2 &|
+infixr 2 |.
+
+-- | Loop a pipe into itself.
+--
+-- *  Will feed all output back to the input
+-- *  Will only ask for input upstream if output is stalled.
+-- *  Yields all outputted values downstream, effectively duplicating them.
+--
+-- @since 0.2.1.0
+feedbackPipe
+    :: Monad m
+    => Pipe x x u m a
+    -> Pipe x x u m a
+feedbackPipe p = fmap fst . runStateP Seq.empty $
+       popper
+    .| hoistPipe lift p
+    .| awaitForever (\x -> lift (modify (:|> x)) *> yield x)
+  where
+    popper = lift get >>= \case
+      Empty -> awaitEither >>= \case
+        Left r  -> pure r
+        Right x -> yield x >> popper
+      x :<| xs -> do
+        lift $ put xs
+        yield x
+        popper
+
 -- | A newtype wrapper over a source (@'Pipe' () o 'Void'@) that gives it an
 -- alternative 'Applicative' and 'Alternative' instance, matching "ListT
 -- done right".
@@ -278,10 +449,15 @@
 
 instance Monad m => Applicative (ZipSource m) where
     pure = ZipSource . yield
-    ZipSource (PipeList fs) <*> ZipSource (PipeList xs) = ZipSource . PipeList . fmap Just $
-            uncurry ($)
-        <$> LT.zip (concatListT fs) (concatListT xs)
+    ZipSource p <*> ZipSource q = ZipSource $ zipSource p q
 
+-- | Takes two sources and runs them in parallel, collating their outputs.
+--
+-- @since 0.2.1.0
+zipSource :: Monad m => Pipe () (a -> b) u m () -> Pipe () a v m () -> Pipe () b w m ()
+zipSource (PipeList fs) (PipeList xs) = PipeList . fmap Just $
+    uncurry ($) <$> LT.zip (concatListT fs) (concatListT xs)
+
 concatListT :: Monad m => ListT m (Maybe a) -> ListT m a
 concatListT xs = ListT $ next xs >>= \case
     Nil              -> pure Nil
@@ -320,27 +496,10 @@
     :: Monad m
     => ListT m (Maybe o)
     -> Pipe i o u m ()
-fromListT = fromRecPipe . go
-  where
-    go xs = FreeT $ next xs >>= \case
-      Nil              -> pure . Pure $ ()
-      Cons Nothing  ys -> pure . Free $ PAwaitF (\_ -> pure ()) $ \_ -> go ys
-      Cons (Just y) ys -> pure . Free $ PYieldF y (go ys)
-
----- | A source is essentially equiavlent to 'ListT'.  This converts
----- a 'ListT' to the source it encodes.
-----
----- See 'ZipSource' for a wrapper over 'Pipe' that gives the right 'Functor'
----- and 'Alternative' instances.
---fromListT
---    :: Monad m
---    => ListT m o
---    -> Pipe i o u m ()
---fromListT = fromRecPipe . go
---  where
---    go xs = FreeT $ next xs >>= \case
---      Nil       -> pure . Pure $ ()
---      Cons y ys -> pure . Free $ PYieldF y (go ys)
+fromListT xs = lift (next xs) >>= \case
+      Nil              -> pure ()
+      Cons Nothing  ys -> fromListT ys
+      Cons (Just y) ys -> yield y *> fromListT ys
 
 -- | Given a "generator" of @o@ in @m@, return a /source/ that that
 -- generator encodes.  Is the inverse of 'withSource'.
diff --git a/src/Data/Conduino/Combinators.hs b/src/Data/Conduino/Combinators.hs
--- a/src/Data/Conduino/Combinators.hs
+++ b/src/Data/Conduino/Combinators.hs
@@ -44,6 +44,7 @@
   -- * Pipes
   , map
   , mapM
+  , iterM
   , scan
   , mapAccum
   , take
@@ -282,6 +283,13 @@
 -- | Map a monadic function to process every input, and yield its output.
 mapM :: Monad m => (i -> m o) -> Pipe i o u m u
 mapM f = awaitForever ((yield =<<) . lift . f)
+
+-- | Execute a monadic function to process every input, passing through the
+-- original value back downstream.
+--
+-- @since 0.2.1.0
+iterM :: Monad m => (i -> m ()) -> Pipe i i u m u
+iterM f = mapM (\x -> x <$ f x)
 
 -- | Map a pure "stateful" function over each incoming item.  Give
 -- a function to update the state and return an output and an initial
diff --git a/src/Data/Conduino/Internal.hs b/src/Data/Conduino/Internal.hs
--- a/src/Data/Conduino/Internal.hs
+++ b/src/Data/Conduino/Internal.hs
@@ -31,14 +31,21 @@
   , hoistPipe
   , RecPipe
   , toRecPipe, fromRecPipe
+  , withRecPipe
+  , runStateP
+  , pAwaitF, pYieldF
   ) where
 
+import           Control.Applicative
+import           Control.Monad.Catch
 import           Control.Monad.Except
 import           Control.Monad.Free.Class
 import           Control.Monad.Free.TH
 import           Control.Monad.RWS
-import           Control.Monad.Trans.Free        (FreeT(..))
+import           Control.Monad.Trans.Free        (FreeT(..), FreeF(..))
 import           Control.Monad.Trans.Free.Church
+import           Control.Monad.Trans.State
+import           Data.Functor
 
 #if !MIN_VERSION_base(4,13,0)
 import           Control.Monad.Fail
@@ -86,22 +93,23 @@
 --    to produce items.  It will pump out items on its own, for pipes
 --    downstream to receive and process.
 --
--- *  If @o@ is 'Void', the pipe is a /sink/ --- it will never 'yield'
---    anything downstream.  It will consume items from things upstream, and
---    produce a result (@a@) if and when it terminates.
+-- *  If @o@ is 'Data.Void.Void', the pipe is a /sink/ --- it will never
+--    'yield' anything downstream.  It will consume items from things
+--    upstream, and produce a result (@a@) if and when it terminates.
 --
--- *  If @u@ is 'Void', then the pipe's upstream is limitless, and never
---    terminates.  This means that you can use 'Data.Condunio.awaitSurely'
---    instead of 'Data.Conduino.await', to get await a value that is
---    guaranteed to come.  You'll get an @i@ instead of a @'Maybe' i@.
+-- *  If @u@ is 'Data.Void.Void', then the pipe's upstream is limitless,
+--    and never terminates.  This means that you can use
+--    'Data.Conduino.awaitSurely' instead of 'Data.Conduino.await', to get
+--    await a value that is guaranteed to come.  You'll get an @i@ instead
+--    of a @'Maybe' i@.
 --
--- *  If @a@ is 'Void', then the pipe never terminates --- it will keep on
---    consuming and/or producing values forever.  If this is a sink, it
---    means that the sink will never terminate, and so
---    'Data.Condunio.runPipe' will also never terminate.  If it is
+-- *  If @a@ is 'Data.Void.Void', then the pipe never terminates --- it
+--    will keep on consuming and/or producing values forever.  If this is
+--    a sink, it means that the sink will never terminate, and so
+--    'Data.Conduino.runPipe' will also never terminate.  If it is
 --    a source, it means that if you chain something downstream with
---    'Data.Condunio..|', that downstream pipe can use 'awaitSurely' to
---    guarantee something being passed down.
+--    'Data.Conduino..|', that downstream pipe can use
+--    'Data.Conduino.awaitSurely' to guarantee something being passed down.
 --
 -- Applicative and Monadic sequencing of pipes chains by exhaustion.
 --
@@ -122,8 +130,8 @@
 -- @
 --
 -- Usually you would use it by chaining together pipes with
--- 'Data.Condunio..|' and then running the result with
--- 'Data.Condunio.runPipe'.
+-- 'Data.Conduino..|' and then running the result with
+-- 'Data.Conduino.runPipe'.
 --
 -- @
 -- 'Data.Conduino.runPipe' $ someSource
@@ -132,11 +140,11 @@
 --        .| someSink
 -- @
 --
--- See 'Data.Condunio..|' and 'Data.Condunio.runPipe' for more information
+-- See 'Data.Conduino..|' and 'Data.Conduino.runPipe' for more information
 -- on usage.
 --
 -- For a "prelude" of commonly used 'Pipe's, see
--- "Data.Condunio.Combinators".
+-- "Data.Conduino.Combinators".
 --
 newtype Pipe i o u m a = Pipe { pipeFree :: FT (PipeF i o u) m a }
   deriving
@@ -151,6 +159,10 @@
     , MonadWriter w
     , MonadError e
     , MonadRWS r w s
+    , Alternative
+    , MonadPlus
+    , MonadThrow
+    , MonadCatch
     )
 
 instance MonadFail m => MonadFail (Pipe i o u m) where
@@ -185,6 +197,10 @@
       PYieldF a x -> PYieldF (g a) x
 
 -- | Transform the underlying monad of a pipe.
+--
+-- Note that if you are trying to work with monad transformers, this is
+-- probably not what you want.  See "Data.Conduino.Lift" for tools for
+-- working with underlying monad transformers.
 hoistPipe
     :: (Monad m, Monad n)
     => (forall x. m x -> n x)
@@ -221,3 +237,69 @@
 -- | Convert a 'RecPipe' back into a 'Pipe'.
 fromRecPipe :: Monad m => RecPipe i o u m a -> Pipe i o u m a
 fromRecPipe = Pipe . toFT
+
+-- | Convenint wrapper over 'toRecPipe' and 'fromRecPipe'.
+--
+-- @since 0.2.1.0
+withRecPipe
+    :: (Monad m, Monad n)
+    => (RecPipe i o u m a -> RecPipe j p v n b)
+    -> Pipe i o u m a
+    -> Pipe j p v n b
+withRecPipe f = fromRecPipe . f . toRecPipe
+
+-- | Turn a 'Pipe' that runs over 'StateT' into a "state-modifying 'Pipe'",
+-- that returns the final state when it terminates.
+--
+-- The main usage of this is to "isolate" the state from other pipes in the
+-- same chain.  For example, of @p@, @q@, and @r@ are all pipes under
+-- 'StateT', then:
+--
+-- @
+--     p
+--  .| q
+--  .| r
+-- @
+--
+-- will all share underlying state, and each can modify the state that they
+-- all three share.  We essentially have global state.
+--
+-- However, if you use 'runStateP', you can all have them use different
+-- encapsulated states.
+--
+-- @
+--     void (runStateP s0 p)
+--  .| void (runStateP s1 q)
+--  .| runStateP s2 r
+-- @
+--
+-- In this case, each of those three chained pipes will use their own
+-- internal states, without sharing.
+--
+-- This is also useful if you want to chain a pipe over 'StateT' with
+-- pipes that don't use state at all: for example if @a@ and @b@ are
+-- "non-stateful" pipes (/not/ over 'StateT'), you can do:
+--
+-- @
+--     a
+--  .| void (runStateP s1 q)
+--  .| b
+-- @
+--
+-- And @a@ and @b@ will be none the wiser to the fact that @q@ uses
+-- 'StateT' internally.
+--
+-- Note to avoid the usage of 'void', 'Data.Conduino.Lift.evalStateP' might
+-- be more useful.
+runStateP
+    :: Monad m
+    => s
+    -> Pipe i o u (StateT s m) a
+    -> Pipe i o u m (a, s)
+runStateP = withRecPipe . go
+  where
+    go s (FreeT p) = FreeT $ runStateT p s <&> \(q, s') ->
+      case q of
+        Pure x -> Pure (x, s')
+        Free l -> Free $ go s' <$> l
+
diff --git a/src/Data/Conduino/Lift.hs b/src/Data/Conduino/Lift.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Conduino/Lift.hs
@@ -0,0 +1,521 @@
+{-# LANGUAGE LambdaCase   #-}
+{-# LANGUAGE ViewPatterns #-}
+
+-- |
+-- Module      : Data.Conduino.Lift
+-- Copyright   : (c) Justin Le 2019
+-- License     : BSD3
+--
+-- Maintainer  : justin@jle.im
+-- Stability   : experimental
+-- Portability : non-portable
+--
+-- Working with underlying monad transformers and 'Pipe'.
+--
+-- There is no "general abstraction" for dealing with each monad
+-- transformer, but we can translate the semantics that each monad
+-- transformer provides into meaningful 'Pipe' operations.
+--
+-- For example, a @'Pipe' i o u ('State' s) a@ is a pipe working over
+-- stateful effects --- it can pull information and modify an underlying
+-- state to do its job.  It takes in @i@ and outputs @o@, using an
+-- underlying state @s@.
+--
+-- However, such a pipe is similar to @s -> 'Pipe'
+-- i o u 'Data.Functor.Identity.Identity' (a, s)@.  Giving some starting
+-- state, it takes in @i@ and outputs @o@, and when it completes, it
+-- returns an @a@ and an @s@, the final state after all its processing is
+-- done.
+--
+-- The /general/ idea is that:
+--
+-- *  A pipe over a monad transformer /shares that monadic context/ over
+--    /every pipe/ in a composition.
+--
+--    For example, if @p@, @q@, and @r@ are all pipes over 'StateT', the @p
+--    .| q .| r@ will all share a common global state.
+--
+--    If @p@, @q@, and @r@ are all pipes over 'ExceptT', then @p .| q .| r@
+--    will all short-circult fail each other: if @q@ fails, then they all
+--    fail, etc.
+--
+--    If @p@, @q@, and @r@ are all pipes over 'WriterT' then @p .| q .| r@
+--    will all accumulate to a shared global log.
+--
+--    If @p@, @q@, and @r@ are all pipes over 'ReaderT' then @p .| q .| r@
+--    will use the same identical environment.
+--
+-- *  Using the @runX@ family of functions ('runStateP', 'runExceptP',
+--    etc.) lets you /isolate/ out the common context within a composition
+--    of pipes.
+--
+--    For example, if @p@ is a pipe over 'StateT', then @a .| 'void' ('runStateP'
+--    s0 p) .| b@, @a@ and @b@ will not be able to use the state of @p@.
+--
+--    If @p@ is a pipe over 'ExceptT', then in @a .| void ('runExceptP' p) .|
+--    b@, a failure in @p@ will not cause all the others to fail.
+--
+-- Both of these representations have different advantages and
+-- disadvantages, that are separate and unique for each individual monad
+-- transformer on a case-by-case basis.  This module provides functions on
+-- such a case-by-case basis as you need them.
+--
+-- @since 0.2.1.0
+module Data.Conduino.Lift (
+  -- * State
+  -- ** Lazy
+    stateP, runStateP, evalStateP, execStateP
+  -- ** Strict
+  , statePS, runStatePS, evalStatePS, execStatePS
+  -- * Except
+  , exceptP, runExceptP, runExceptP_
+  -- * Reader
+  , readerP, runReaderP
+  -- * Writer
+  -- ** Lazy
+  , writerP, runWriterP, execWriterP
+  -- ** Strict
+  , writerPS, runWriterPS, execWriterPS
+  -- * RWS
+  -- ** Lazy
+  , rwsP, runRWSP, evalRWSP, execRWSP
+  -- ** Strict
+  , rwsPS, runRWSPS, evalRWSPS, execRWSPS
+  -- * Catch
+  , catchP, runCatchP
+  ) where
+
+import           Control.Monad.Catch.Pure
+import           Control.Monad.Trans.Class
+import           Control.Monad.Trans.Except
+import           Control.Monad.Trans.Free
+import           Control.Monad.Trans.RWS           (RWST(..))
+import           Control.Monad.Trans.Reader
+import           Control.Monad.Trans.State
+import           Control.Monad.Trans.Writer
+import           Data.Conduino
+import           Data.Conduino.Internal
+import           Data.Functor
+import qualified Control.Monad.Trans.RWS           as RWS
+import qualified Control.Monad.Trans.RWS.Strict    as RWSS
+import qualified Control.Monad.Trans.State.Strict  as SS
+import qualified Control.Monad.Trans.Writer.Strict as WS
+
+-- | Turn a "state-modifying 'Pipe'" into a 'Pipe' that runs over 'StateT',
+-- so you can chain it with other 'StateT' pipes.
+--
+-- Note that this will /overwrite/ whatever state exists with
+-- the @s@ that it gets when it terminates.  If any other pipe in this
+-- chain modifies or uses state, all modifications will be overwritten when
+-- the @(a, s)@-producing pipe terminates.
+stateP
+    :: Monad m
+    => (s -> Pipe i o u m (a, s))
+    -> Pipe i o u (StateT s m) a
+stateP f = do
+    s       <- lift get
+    (x, s') <- hoistPipe lift (f s)
+    x <$ lift (put s')
+
+-- | Like 'runStateP', but ignoring the final result.  It returns the final
+-- state after the pipe succesfuly terminates.
+execStateP
+    :: Monad m
+    => s
+    -> Pipe i o u (StateT s m) a
+    -> Pipe i o u m s
+execStateP s = fmap snd . runStateP s
+
+-- | Takes a 'Pipe' over 'StateT' and "hides" the state from the outside
+-- world.  Give an initial state --- the pipe behaves the same way, but to
+-- the external user it is abstracted away.  See 'runStateP' for more
+-- information.
+--
+-- This can be cleaner than 'runStateP' because if @a@ is @()@, you
+-- don't have to sprinkle in 'void' everywhere.  However, it's only really
+-- useful if you don't need to get the final state upon termination.
+evalStateP
+    :: Monad m
+    => s
+    -> Pipe i o u (StateT s m) a
+    -> Pipe i o u m a
+evalStateP s = fmap fst . runStateP s
+
+-- | 'stateP', but for "Control.Monad.Trans.State.Strict".
+statePS
+    :: Monad m
+    => (s -> Pipe i o u m (a, s))
+    -> Pipe i o u (SS.StateT s m) a
+statePS f = do
+    s       <- lift SS.get
+    (x, s') <- hoistPipe lift (f s)
+    x <$ lift (SS.put s')
+
+-- | 'runStateP', but for "Control.Monad.Trans.State.Strict".
+runStatePS
+    :: Monad m
+    => s
+    -> Pipe i o u (SS.StateT s m) a
+    -> Pipe i o u m (a, s)
+runStatePS = withRecPipe . go
+  where
+    go s (FreeT p) = FreeT $ SS.runStateT p s <&> \(q, s') ->
+      case q of
+        Pure x -> Pure (x, s')
+        Free l -> Free $ go s' <$> l
+
+-- | 'execStateP', but for "Control.Monad.Trans.State.Strict".
+execStatePS
+    :: Monad m
+    => s
+    -> Pipe i o u (SS.StateT s m) a
+    -> Pipe i o u m s
+execStatePS s = fmap snd . runStatePS s
+
+-- | 'evalStateP', but for "Control.Monad.Trans.State.Strict".
+evalStatePS
+    :: Monad m
+    => s
+    -> Pipe i o u (SS.StateT s m) a
+    -> Pipe i o u m a
+evalStatePS s = fmap fst . runStatePS s
+
+-- | Turn a "failable-result" 'Pipe' into a pipe over 'ExceptT'.
+--
+-- Note that a 'throwE' failure will only ever happen when the input pipe
+-- "succesfully" terminates with 'Left'.  It would never happen before the
+-- pipe terminates, since you don't get the @'Either' e a@ until the pipe
+-- succesfully terminates.
+exceptP
+    :: Monad m
+    => Pipe i o u m (Either e a)
+    -> Pipe i o u (ExceptT e m) a
+exceptP p = hoistPipe lift p >>= \case
+    Left  e -> lift $ throwE e
+    Right x -> pure x
+
+-- | Turn a 'Pipe' that runs over 'ExceptT' into an "early-terminating
+-- 'Pipe'" that "succesfully" returns 'Left' or 'Right'.
+--
+-- The main usage of this is to "isolate" the short-circuiting failure of
+-- 'ExceptT' to only happen within one component of a chain.  For example,
+-- of @p@, @q@, and @r@ are all pipes under 'ExceptT', then:
+--
+-- @
+--     p
+--  .| q
+--  .| r
+-- @
+--
+-- will short-circuit fail if /any/ of @p@, @q@, or @r@ fail.  We have
+-- global failure only.
+--
+-- However, if you use 'runExceptP', we isolate the short-circuiting
+-- failure to only a single type.
+--
+-- @
+--     void (runExceptP p)
+--  .| void (runExceptP q)
+--  .| runExceptP r
+-- @
+--
+-- In this case, if (for example) @q@ fails, it won't cause the whole thing
+-- to fail: it will just be the same as if @q@ succesfully terminates
+-- normally.
+--
+-- This is also useful if you want to chain a pipe over 'ExceptT' with
+-- pipes that don't have 'ExceptT' at all: for example if @a@ and @b@ are
+-- "non-erroring" pipes (/not/ over 'ExceptT'), you can do:
+--
+-- @
+--     a
+--  .| void (runExceptP q)
+--  .| b
+-- @
+--
+-- And @a@ and @b@ will be none the wiser to the fact that @q@ uses
+-- 'ExceptT' internally.
+--
+-- Note to avoid the usage of 'void', 'runExceptP_' might be more useful.
+runExceptP
+    :: Monad m
+    => Pipe i o u (ExceptT e m) a
+    -> Pipe i o u m (Either e a)
+runExceptP = withRecPipe go
+  where
+    go (FreeT p) = FreeT $ runExceptT p <&> \case
+      Left  e        -> Pure $ Left e
+      Right (Pure x) -> Pure $ Right x
+      Right (Free l) -> Free $ go <$> l
+
+-- | A handy version of 'runExceptP' that discards its output, so it can be
+-- easier to chain using '.|'.  It's useful if you are using 'runExceptP'
+-- to "isolate" failures from the rest of a chain.
+runExceptP_
+    :: Monad m
+    => Pipe i o u (ExceptT e m) a
+    -> Pipe i o u m ()
+runExceptP_ = void . runExceptP
+
+-- | Like 'exceptP', but for 'CatchT'.  See 'exceptP' for usage details and
+-- caveats.  In general, can be useful for chaining with other 'CatchT'
+-- pipes.
+--
+-- Note that a 'throwM' failure will only ever happen when the input pipe
+-- "succesfully" terminates with 'Left'.  It would never happen before the
+-- pipe terminates, since you don't get the @'Either' 'SomeException' a@
+-- until the pipe succesfully terminates.
+catchP
+    :: Monad m
+    => Pipe i o u m (Either SomeException a)
+    -> Pipe i o u (CatchT m) a
+catchP p = hoistPipe lift p >>= \case
+    Left  e -> lift $ throwM e
+    Right x -> pure x
+
+-- | Like 'runExceptP', but for 'CatchT'.  See 'runExceptP' for usage
+-- details.  In general, can be useful for "isolating" a 'CatchT' pipe from
+-- the rest of its chain.
+runCatchP
+    :: Monad m
+    => Pipe i o u (CatchT m) a
+    -> Pipe i o u m (Either SomeException a)
+runCatchP = withRecPipe go
+  where
+    go (FreeT p) = FreeT $ runCatchT p <&> \case
+      Left  e        -> Pure $ Left e
+      Right (Pure x) -> Pure $ Right x
+      Right (Free l) -> Free $ go <$> l
+
+-- | Turn a "parameterized 'Pipe'" into a 'Pipe' that runs over 'ReaderT',
+-- so you can chain it with other 'ReaderT' pipes.
+--
+-- Essentially, instead of directly providing the @r@ in an @r -> 'Pipe'
+-- i o u m a@, the @r@ instead comes from the globally shared environment.
+readerP
+    :: Monad m
+    => (r -> Pipe i o u m a)
+    -> Pipe i o u (ReaderT r m) a
+readerP f = hoistPipe lift . f =<< lift ask
+
+-- | Turn a pipe over 'ReaderT' into a directly parameterized pipe.
+-- Instead of getting the parameter from the globally shared 'ReaderT'
+-- environment, give it directly instead.
+--
+-- It can be useful to "ignore" a globally shared environment and just give
+-- the @r@ directly and immediately.
+runReaderP
+    :: Monad m
+    => r
+    -> Pipe i o u (ReaderT r m) a
+    -> Pipe i o u m a
+runReaderP r = hoistPipe (`runReaderT` r)
+
+-- | Turn a pipe returning an @(a, w)@ tuple upon termination into a pipe
+-- returning @a@, logging the @w@ in an underlying 'WriterT' context.
+--
+-- This can be useful for composing your pipe with other 'WriterT' pipes,
+-- aggregating all to a common global log.
+--
+-- However, be aware that this only ever 'tell's when the pipe succesfuly
+-- terminates.  It doesn't do "streaming logging" -- it only makes one
+-- log payload at the point of succesful termination.  To do streaming
+-- logging (logging things as you get them), you should probably just
+-- directly use 'WriterT' instead, with 'Data.Conduino.Combinators.repeatM'
+-- or 'Data.Conduino.Combinators.iterM' or something similar.
+writerP
+    :: (Monad m, Monoid w)
+    => Pipe i o u m (a, w)
+    -> Pipe i o u (WriterT w m) a
+writerP p = do
+    (x, w) <- hoistPipe lift p
+    x <$ lift (tell w)
+
+-- | Turn a 'Pipe' that runs over 'WriterT' into a 'Pipe' that returns the
+-- final log when it terminates.
+--
+-- The main usage of this is to "isolate" the log from other pipes in the
+-- same chain.  For example, of @p@, @q@, and @r@ are all pipes under
+-- 'WriterT', then:
+--
+-- @
+--     p
+--  .| q
+--  .| r
+-- @
+--
+-- will all share underlying log, and all logging from any of them will
+-- accumulate together in an interleaved way.  It is essentially a global
+-- log.
+--
+-- However, if you use 'runWriterP', you can all have them use different
+-- encapsulated logs.
+--
+-- @
+--     void (runWriterP p)
+--  .| void (runWriterP q)
+--  .| runWriterP r
+-- @
+--
+-- In this case, each of those three chained pipes will use their own
+-- internal logs, without sharing.
+--
+-- This is also useful if you want to chain a pipe over 'WriterT' with
+-- pipes that don't use state at all: for example if @a@ and @b@ are
+-- "non-logging" pipes (/not/ over 'WriterT'), you can do:
+--
+-- @
+--     a
+--  .| void (runWriterP q)
+--  .| b
+-- @
+--
+-- And @a@ and @b@ will be none the wiser to the fact that @q@ uses
+-- 'WriterT' internally.
+runWriterP
+    :: (Monad m, Monoid w)
+    => Pipe i o u (WriterT w m) a
+    -> Pipe i o u m (a, w)
+runWriterP = withRecPipe (go mempty)
+  where
+    go w (FreeT p) = FreeT $ runWriterT p <&> \(r, (w <>)->w') ->
+      case r of
+        Pure x -> Pure (x, w')
+        Free l -> Free $ go w' <$> l
+
+-- | 'runWriterP', but only returning the final log after succesful
+-- termination.
+execWriterP
+    :: (Monad m, Monoid w)
+    => Pipe i o u (WriterT w m) a
+    -> Pipe i o u m w
+execWriterP = fmap snd . runWriterP
+
+-- | 'writerP', but for "Control.Monad.Trans.Writer.Strict".
+writerPS
+    :: (Monad m, Monoid w)
+    => Pipe i o u m (a, w)
+    -> Pipe i o u (WS.WriterT w m) a
+writerPS p = do
+    (x, w) <- hoistPipe lift p
+    x <$ lift (WS.tell w)
+
+-- | 'runWriterP', but for "Control.Monad.Trans.Writer.Strict".
+runWriterPS
+    :: (Monad m, Monoid w)
+    => Pipe i o u (WS.WriterT w m) a
+    -> Pipe i o u m (a, w)
+runWriterPS = withRecPipe (go mempty)
+  where
+    go w (FreeT p) = FreeT $ WS.runWriterT p <&> \(r, (w <>)->w') ->
+      case r of
+        Pure x -> Pure (x, w')
+        Free l -> Free $ go w' <$> l
+
+-- | 'execWriterP', but for "Control.Monad.Trans.Writer.Strict".
+execWriterPS
+    :: (Monad m, Monoid w)
+    => Pipe i o u (WriterT w m) a
+    -> Pipe i o u m w
+execWriterPS = fmap snd . runWriterP
+
+-- | Turn a parameterized, state-transforming, log-producing 'Pipe' into
+-- a 'Pipe' over 'RWST', which can be useful for chaining it with other
+-- 'RWST' pipes.
+--
+-- See 'stateP' and 'writerP' for more details on caveats, including:
+--
+-- *  Logging only happens when the @(a,s,w)@-returning pipe terminates.
+--    There is no "streaming logging" --- the resulting @w@ is logged all
+--    at once.
+-- *  When the @(a,s,w)@-returning pipe terminates, whatever state in the
+--    'RWST' is overwritten with the @s@ returned.  If other pipes in the
+--    chain modify the @s@, their modifications will be overwritten.
+rwsP
+    :: (Monad m, Monoid w)
+    => (r -> s -> Pipe i o u m (a, s, w))
+    -> Pipe i o u (RWST r w s m) a
+rwsP f = do
+    r <- lift RWS.ask
+    s <- lift RWS.get
+    (x, s', w) <- hoistPipe lift (f r s)
+    lift (RWS.tell w)
+    x <$ lift (RWS.put s')
+
+-- | Turn a 'Pipe' that runs over 'RWST' into a state-modifying,
+-- environment-using, log-accumulating 'Pipe'.  See 'runStateP',
+-- 'runWriterP', and 'runReaderP' for the uses and semantics.
+runRWSP
+    :: (Monad m, Monoid w)
+    => r
+    -> s
+    -> Pipe i o u (RWST r w s m) a
+    -> Pipe i o u m (a, s, w)
+runRWSP r = withRecPipe . go mempty
+  where
+    go w s (FreeT p) = FreeT $ runRWST p r s <&> \(q, s', (w <>)->w') ->
+      case q of
+        Pure x -> Pure (x, s', w')
+        Free l -> Free $ go w' s' <$> l
+
+-- | 'runRWSP', but ignoring the final state.
+evalRWSP
+    :: (Monad m, Monoid w)
+    => r
+    -> s
+    -> Pipe i o u (RWST r w s m) a
+    -> Pipe i o u m (a, w)
+evalRWSP r s = fmap (\(x,_,w) -> (x,w)) . runRWSP r s
+
+-- | 'runRWSP', but ignoring the result value.
+execRWSP
+    :: (Monad m, Monoid w)
+    => r
+    -> s
+    -> Pipe i o u (RWST r w s m) a
+    -> Pipe i o u m (s, w)
+execRWSP r s = fmap (\(_,s',w) -> (s',w)) . runRWSP r s
+
+-- | 'rwsP', but for "Control.Monad.Trans.RWS.Strict".
+rwsPS
+    :: (Monad m, Monoid w)
+    => (r -> s -> Pipe i o u m (a, s, w))
+    -> Pipe i o u (RWSS.RWST r w s m) a
+rwsPS f = do
+    r <- lift RWSS.ask
+    s <- lift RWSS.get
+    (x, s', w) <- hoistPipe lift (f r s)
+    lift (RWSS.tell w)
+    x <$ lift (RWSS.put s')
+
+-- | 'runRWSPS', but for "Control.Monad.Trans.RWS.Strict".
+runRWSPS
+    :: (Monad m, Monoid w)
+    => r
+    -> s
+    -> Pipe i o u (RWSS.RWST r w s m) a
+    -> Pipe i o u m (a, s, w)
+runRWSPS r = withRecPipe . go mempty
+  where
+    go w s (FreeT p) = FreeT $ RWSS.runRWST p r s <&> \(q, s', (w <>)->w') ->
+      case q of
+        Pure x -> Pure (x, s', w')
+        Free l -> Free $ go w' s' <$> l
+
+-- | 'evalRWSPS', but for "Control.Monad.Trans.RWS.Strict".
+evalRWSPS
+    :: (Monad m, Monoid w)
+    => r
+    -> s
+    -> Pipe i o u (RWSS.RWST r w s m) a
+    -> Pipe i o u m (a, w)
+evalRWSPS r s = fmap (\(x,_,w) -> (x,w)) . runRWSPS r s
+
+-- | 'execRWSPS', but for "Control.Monad.Trans.RWS.Strict".
+execRWSPS
+    :: (Monad m, Monoid w)
+    => r
+    -> s
+    -> Pipe i o u (RWSS.RWST r w s m) a
+    -> Pipe i o u m (s, w)
+execRWSPS r s = fmap (\(_,s',w) -> (s',w)) . runRWSPS r s
