diff --git a/pipes-bytestring.cabal b/pipes-bytestring.cabal
--- a/pipes-bytestring.cabal
+++ b/pipes-bytestring.cabal
@@ -1,5 +1,5 @@
 Name: pipes-bytestring
-Version: 1.0.1
+Version: 1.0.2
 Cabal-Version: >=1.8.0.2
 Build-Type: Simple
 License: BSD3
@@ -23,5 +23,7 @@
         pipes        >= 4.0     && < 4.1 ,
         pipes-parse  >= 2.0.0   && < 2.1 ,
         transformers >= 0.2.0.0 && < 0.4
-    Exposed-Modules: Pipes.ByteString
+    Exposed-Modules:
+        Pipes.ByteString,
+        Pipes.ByteString.Parse
     GHC-Options: -O2 -Wall
diff --git a/src/Pipes/ByteString.hs b/src/Pipes/ByteString.hs
--- a/src/Pipes/ByteString.hs
+++ b/src/Pipes/ByteString.hs
@@ -1,5 +1,10 @@
-{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RankNTypes, CPP #-}
 
+-- The rewrite rules require the Trustworthy annotation
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
+
 {-| This module provides @pipes@ utilities for \"byte streams\", which are
     streams of strict 'ByteString's chunks.  Use byte streams to interact
     with both 'IO.Handle's and lazy 'ByteString's.
@@ -52,91 +57,93 @@
 
 module Pipes.ByteString (
     -- * Producers
-    fromLazy,
-    stdin,
-    fromHandle,
-    hGetSome,
-    hGet,
+      fromLazy
+    , stdin
+    , fromHandle
+    , hGetSome
+    , hGet
+    , pack
 
     -- * Servers
-    hGetSomeN,
-    hGetN,
+    , hGetSomeN
+    , hGetN
 
     -- * Consumers
-    stdout,
-    toHandle,
+    , stdout
+    , toHandle
 
     -- * Pipes
-    map,
-    concatMap,
-    take,
-    drop,
-    takeWhile,
-    dropWhile,
-    filter,
-    elemIndices,
-    findIndices,
-    scan,
+    , map
+    , concatMap
+    , take
+    , drop
+    , takeWhile
+    , dropWhile
+    , filter
+    , elemIndices
+    , findIndices
+    , scan
+    , unpack
 
     -- * Folds
-    toLazy,
-    toLazyM,
-    fold,
-    head,
-    last,
-    null,
-    length,
-    any,
-    all,
-    maximum,
-    minimum,
-    elem,
-    notElem,
-    find,
-    index,
-    elemIndex,
-    findIndex,
-    count,
+    , toLazy
+    , toLazyM
+    , fold
+    , head
+    , last
+    , null
+    , length
+    , any
+    , all
+    , maximum
+    , minimum
+    , elem
+    , notElem
+    , find
+    , index
+    , elemIndex
+    , findIndex
+    , count
 
     -- * Splitters
-    splitAt,
-    chunksOf,
-    span,
-    break,
-    splitWith,
-    split,
-    groupBy,
-    group,
-    lines,
-    words,
+    , splitAt
+    , chunksOf
+    , span
+    , break
+    , splitWith
+    , split
+    , groupBy
+    , group
+    , lines
+    , words
 
     -- * Transformations
-    intersperse,
+    , intersperse
 
     -- * Joiners
-    intercalate,
-    unlines,
-    unwords,
+    , intercalate
+    , unlines
+    , unwords
 
     -- * Low-level Parsers
     -- $parse
-    nextByte,
-    drawByte,
-    unDrawByte,
-    peekByte,
-    isEndOfBytes,
-    takeWhile',
+    , nextByte
+    , drawByte
+    , unDrawByte
+    , peekByte
+    , isEndOfBytes
+    , takeWhile'
 
     -- * Re-exports
     -- $reexports
-    module Data.ByteString,
-    module Data.Word,
-    module Pipes.Parse
+    , module Data.ByteString
+    , module Data.Word
+    , module Pipes.Parse
     ) where
 
 import Control.Exception (throwIO, try)
 import Control.Monad (liftM)
-import Control.Monad.Trans.State.Strict (StateT, modify)
+import Control.Monad.Trans.State.Strict (StateT)
 import qualified Data.ByteString as BS
 import Data.ByteString (ByteString)
 import Data.ByteString.Internal (isSpaceWord8)
@@ -150,37 +157,42 @@
 import Foreign.C.Error (Errno(Errno), ePIPE)
 import qualified GHC.IO.Exception as G
 import Pipes
+import qualified Pipes.ByteString.Parse as PBP
+import Pipes.ByteString.Parse (
+    nextByte, drawByte, unDrawByte, peekByte, isEndOfBytes )
 import Pipes.Core (respond, Server')
+import Pipes.Lift (evalStateP)
 import qualified Pipes.Parse as PP
-import Pipes.Parse (input, concat, FreeT)
+import Pipes.Parse (input, concat, FreeT, isEndOfInput)
 import qualified Pipes.Prelude as P
 import qualified System.IO as IO
 import Prelude hiding (
-    all,
-    any,
-    break,
-    concat,
-    concatMap,
-    drop,
-    dropWhile,
-    elem,
-    filter,
-    head,
-    last,
-    lines,
-    length,
-    map,
-    maximum,
-    minimum,
-    notElem,
-    null,
-    span,
-    splitAt,
-    take,
-    takeWhile,
-    unlines,
-    unwords,
-    words )
+      all
+    , any
+    , break
+    , concat
+    , concatMap
+    , drop
+    , dropWhile
+    , elem
+    , filter
+    , head
+    , last
+    , lines
+    , length
+    , map
+    , maximum
+    , minimum
+    , notElem
+    , null
+    , span
+    , splitAt
+    , take
+    , takeWhile
+    , unlines
+    , unwords
+    , words
+    )
 
 -- | Convert a lazy 'BL.ByteString' into a 'Producer' of strict 'ByteString's
 fromLazy :: (Monad m) => BL.ByteString -> Producer' ByteString m ()
@@ -232,6 +244,19 @@
                 go
 {-# INLINABLE hGet #-}
 
+-- | Convert a 'Word8' producer into a byte stream using a default chunk size
+pack :: Monad m => Producer Word8 m () -> Producer ByteString m ()
+pack p = evalStateP p go where
+    go = do
+        eof <- lift isEndOfInput
+        if eof
+            then return ()
+            else do
+                bytes <- lift $ P.toListM (P.take defaultChunkSize <-< input)
+                yield $ BS.pack bytes
+                go
+{-# INLINABLE pack #-}
+
 {-| Like 'hGetSome', except you can vary the maximum chunk size for each request
 -}
 hGetSomeN :: MonadIO m => IO.Handle -> Int -> Server' Int ByteString m ()
@@ -290,6 +315,10 @@
 toHandle h = for cat (liftIO . BS.hPut h)
 {-# INLINABLE toHandle #-}
 
+{-# RULES "p >-> toHandle h" forall p h .
+        p >-> toHandle h = for p (\bs -> liftIO (BS.hPut h bs))
+  #-}
+
 -- | Apply a transformation to each 'Word8' in the stream
 map :: (Monad m) => (Word8 -> Word8) -> Pipe ByteString ByteString m r
 map f = P.map (BS.map f)
@@ -316,7 +345,7 @@
                     go (n - len)
 {-# INLINABLE take #-}
 
--- | @(dropD n)@ drops the first @n@ bytes
+-- | @(drop n)@ drops the first @n@ bytes
 drop :: (Monad m, Integral a) => a -> Pipe ByteString ByteString m r
 drop n0 = go n0 where
     go n
@@ -391,6 +420,16 @@
         go w8'
 {-# INLINABLE scan #-}
 
+-- | Unpack the bytes
+unpack :: Monad m => Pipe ByteString Word8 m ()
+unpack = for cat (mapM_ yield . BS.unpack)
+{-# INLINABLE unpack #-}
+
+{-# RULES
+    "p >-> unpack" forall p .
+        p >-> unpack = for p (\bs -> mapM_ yield (BS.unpack bs))
+  #-}
+
 {-| Fold a pure 'Producer' of strict 'ByteString's into a lazy
     'BL.ByteString'
 -}
@@ -422,9 +461,9 @@
   where
     go p = do
         x <- nextByte p
-        case x of
-            Left   _      -> return  Nothing
-            Right (w8, _) -> return (Just w8)
+        return $ case x of
+            Left   _      -> Nothing
+            Right (w8, _) -> Just w8
 {-# INLINABLE head #-}
 
 -- | Retrieve the last 'Word8'
@@ -436,9 +475,7 @@
         case x of
             Left   ()      -> return r
             Right (bs, p') ->
-                if (BS.null bs)
-                then go r p'
-                else go (Just $ BS.last bs) p'
+                go (if BS.null bs then r else (Just $ BS.last bs)) p'
                 -- TODO: Change this to 'unsafeLast' when bytestring-0.10.2.0
                 --       becomes more widespread
 {-# INLINABLE last #-}
@@ -560,15 +597,15 @@
 chunksOf
     :: (Monad m, Integral n)
     => n -> Producer ByteString m r -> FreeT (Producer ByteString m) m r
-chunksOf n p0 = PP.FreeT (go p0)
+chunksOf n = go
   where
-    go p = do
+    go p = PP.FreeT $ do
         x <- next p
         return $ case x of
             Left   r       -> PP.Pure r
             Right (bs, p') -> PP.Free $ do
                 p'' <- splitAt n (yield bs >> p')
-                return $ PP.FreeT (go p'')
+                return (go p'')
 {-# INLINABLE chunksOf #-}
 
 {-| Split a byte stream in two, where the first byte stream is the longest
@@ -577,7 +614,7 @@
 span
     :: (Monad m)
     => (Word8 -> Bool)
-    -> Producer ByteString m r
+    -> Producer  ByteString m  r
     -> Producer' ByteString m (Producer ByteString m r)
 span predicate = go
   where
@@ -602,7 +639,7 @@
 break
     :: (Monad m)
     => (Word8 -> Bool)
-    -> Producer ByteString m r
+    -> Producer ByteString m  r
     -> Producer ByteString m (Producer ByteString m r)
 break predicate = span (not . predicate)
 {-# INLINABLE break #-}
@@ -624,16 +661,14 @@
             Right (bs, p') ->
                 if (BS.null bs)
                 then go0 p'
-                else return $ PP.Free $ do
-                    p'' <- span (not . predicate) (yield bs >> p')
-                    return $ PP.FreeT (go1 p'')
-    go1 p = do
-        x <- nextByte p
-        return $ case x of
-            Left   r      -> PP.Pure r
-            Right (_, p') -> PP.Free $ do
-                    p'' <- span (not . predicate) p'
-                    return $ PP.FreeT (go1 p'')
+                else go1 (yield bs >> p')
+    go1 p = return $ PP.Free $ do
+        p' <- span (not . predicate) p
+        return $ PP.FreeT $ do
+            x <- nextByte p'
+            case x of
+                Left   r       -> return (PP.Pure r)
+                Right (_, p'') -> go1 p''
 {-# INLINABLE splitWith #-}
 
 -- | Split a byte stream using the given 'Word8' as the delimiter
@@ -641,7 +676,7 @@
       => Word8
       -> Producer ByteString m r
       -> FreeT (Producer ByteString m) m r
-split w8 = splitWith (w8 /=)
+split w8 = splitWith (w8 ==)
 {-# INLINABLE split #-}
 
 {-| Group a byte stream into 'FreeT'-delimited byte streams using the supplied
@@ -692,12 +727,11 @@
                 else return $ PP.Free $ go1 (yield bs >> p')
     go1 p = do
         p' <- break (fromIntegral (ord '\n') ==) p
-        return $ PP.FreeT (go2 p')
-    go2 p = do
-        x  <- nextByte p
-        return $ case x of
-            Left   r      -> PP.Pure r
-            Right (_, p') -> PP.Free (go1 p')
+        return $ PP.FreeT $ do
+            x  <- nextByte p'
+            case x of
+                Left   r       -> return (PP.Pure r)
+                Right (_, p'') -> go0 p''
 {-# INLINABLE lines #-}
 
 {-| Split a byte stream into 'FreeT'-delimited words
@@ -708,20 +742,15 @@
 -}
 words
     :: (Monad m) => Producer ByteString m r -> FreeT (Producer ByteString m) m r
-words p0 = removeEmpty (splitWith isSpaceWord8 p0)
+words = go
   where
-    removeEmpty f = PP.FreeT $ do
-        x <- PP.runFreeT f
-        case x of
-            PP.Pure r -> return (PP.Pure r)
-            PP.Free p -> do
-                y <- next p
-                case y of
-                    Left   f'      -> PP.runFreeT (removeEmpty f')
-                    Right (bs, p') -> return $ PP.Free $ do
-                        yield bs
-                        f' <- p'
-                        return (removeEmpty f')
+    go p = PP.FreeT $ do
+        x <- next (p >-> dropWhile isSpaceWord8)
+        return $ case x of
+            Left   r       -> PP.Pure r
+            Right (bs, p') -> PP.Free $ do
+                p'' <- break isSpaceWord8 (yield bs >> p')
+                return (go p'')
 {-# INLINABLE words #-}
 
 -- | Intersperse a 'Word8' in between the bytes of the byte stream
@@ -809,81 +838,6 @@
     in @pipes-parse@.
 -}
 
-{-| Consume the first byte from a byte stream
-
-    'next' either fails with a 'Left' if the 'Producer' has no more bytes or
-    succeeds with a 'Right' providing the next byte and the remainder of the
-    'Producer'.
--}
-nextByte
-    :: (Monad m)
-    => Producer ByteString m r
-    -> m (Either r (Word8, Producer ByteString m r))
-nextByte = go
-  where
-    go p = do
-        x <- next p
-        case x of
-            Left   r       -> return (Left r)
-            Right (bs, p') -> case (BS.uncons bs) of
-                Nothing        -> go p'
-                Just (w8, bs') -> return (Right (w8, yield bs' >> p'))
-{-# INLINABLE nextByte #-}
-
-{-| Draw one 'Word8' from the underlying 'Producer', returning 'Left' if the
-    'Producer' is empty
--}
-drawByte :: (Monad m) => StateT (Producer ByteString m r) m (Either r Word8)
-drawByte = do
-    x <- PP.draw
-    case x of
-        Left  r  -> return (Left r)
-        Right bs -> case (BS.uncons bs) of
-            Nothing        -> drawByte
-            Just (w8, bs') -> do
-                PP.unDraw bs'
-                return (Right w8)
-{-# INLINABLE drawByte #-}
-
--- | Push back a 'Word8' onto the underlying 'Producer'
-unDrawByte :: (Monad m) => Word8 -> StateT (Producer ByteString m r) m ()
-unDrawByte w8 = modify (yield (BS.singleton w8) >>)
-{-# INLINABLE unDrawByte #-}
-
-{-| 'peekByte' checks the first 'Word8' in the stream, but uses 'unDrawByte' to
-    push the 'Word8' back
-
-> peekByte = do
->     x <- drawByte
->     case x of
->         Left  _  -> return ()
->         Right w8 -> unDrawByte w8
->     return x
--}
-peekByte :: (Monad m) => StateT (Producer ByteString m r) m (Either r Word8)
-peekByte = do
-    x <- drawByte
-    case x of
-        Left  _  -> return ()
-        Right w8 -> unDrawByte w8
-    return x
-{-# INLINABLE peekByte #-}
-
-{-| Check if the underlying 'Producer' has no more bytes
-
-    Note that this will skip over empty 'ByteString' chunks, unlike
-    'PP.isEndOfInput' from @pipes-parse@.
-
-> isEndOfBytes = liftM isLeft peekByte
--}
-isEndOfBytes :: (Monad m) => StateT (Producer ByteString m r) m Bool
-isEndOfBytes = do
-    x <- peekByte
-    return (case x of
-        Left  _ -> True
-        Right _ -> False )
-{-# INLINABLE isEndOfBytes #-}
-
 {-| Take bytes until they fail the predicate
 
     Unlike 'takeWhile', this 'PP.unDraw's unused bytes
@@ -892,21 +846,14 @@
     :: (Monad m)
     => (Word8 -> Bool)
     -> Pipe ByteString ByteString (StateT (Producer ByteString m r) m) ()
-takeWhile' predicate = go
-  where
-    go = do
-        bs <- await
-        let (prefix, suffix) = BS.span predicate bs
-        if (BS.null suffix)
-            then do
-                yield bs
-                go
-            else do
-                lift $ PP.unDraw suffix
-                yield prefix
+takeWhile' = PBP.takeWhile
 {-# INLINABLE takeWhile' #-}
+{-# DEPRECATED takeWhile' "Use Pipes.ByteString.Parse.takeWhile instead" #-}
 
 {- $reexports
+    "Pipes.ByteString.Parse" re-exports 'nextByte', 'drawByte', 'unDrawByte',
+    'peekByte', and 'isEndOfBytes'.
+    
     @Data.ByteString@ re-exports the 'ByteString' type.
 
     @Data.Word@ re-exports the 'Word8' type.
diff --git a/src/Pipes/ByteString/Parse.hs b/src/Pipes/ByteString/Parse.hs
new file mode 100644
--- /dev/null
+++ b/src/Pipes/ByteString/Parse.hs
@@ -0,0 +1,142 @@
+-- | Parsing utilities for bytestrings, in the style of @pipes-parse@
+
+module Pipes.ByteString.Parse (
+    -- * Parsers
+      nextByte
+    , drawByte
+    , unDrawByte
+    , peekByte
+    , isEndOfBytes
+    , take
+    , takeWhile
+    ) where
+
+import Control.Monad.Trans.State.Strict (StateT, modify)
+import qualified Data.ByteString as BS
+import Data.ByteString (ByteString)
+import Data.ByteString.Unsafe (unsafeTake, unsafeDrop)
+import Data.Word (Word8)
+import Pipes
+import qualified Pipes.Parse as PP
+
+import Prelude hiding (take, takeWhile)
+
+{-| Consume the first byte from a byte stream
+
+    'next' either fails with a 'Left' if the 'Producer' has no more bytes or
+    succeeds with a 'Right' providing the next byte and the remainder of the
+    'Producer'.
+-}
+nextByte
+    :: (Monad m)
+    => Producer ByteString m r
+    -> m (Either r (Word8, Producer ByteString m r))
+nextByte = go
+  where
+    go p = do
+        x <- next p
+        case x of
+            Left   r       -> return (Left r)
+            Right (bs, p') -> case (BS.uncons bs) of
+                Nothing        -> go p'
+                Just (w8, bs') -> return (Right (w8, yield bs' >> p'))
+{-# INLINABLE nextByte #-}
+
+{-| Draw one 'Word8' from the underlying 'Producer', returning 'Left' if the
+    'Producer' is empty
+-}
+drawByte :: (Monad m) => StateT (Producer ByteString m r) m (Either r Word8)
+drawByte = do
+    x <- PP.draw
+    case x of
+        Left  r  -> return (Left r)
+        Right bs -> case (BS.uncons bs) of
+            Nothing        -> drawByte
+            Just (w8, bs') -> do
+                PP.unDraw bs'
+                return (Right w8)
+{-# INLINABLE drawByte #-}
+
+-- | Push back a 'Word8' onto the underlying 'Producer'
+unDrawByte :: (Monad m) => Word8 -> StateT (Producer ByteString m r) m ()
+unDrawByte w8 = modify (yield (BS.singleton w8) >>)
+{-# INLINABLE unDrawByte #-}
+
+{-| 'peekByte' checks the first 'Word8' in the stream, but uses 'unDrawByte' to
+    push the 'Word8' back
+
+> peekByte = do
+>     x <- drawByte
+>     case x of
+>         Left  _  -> return ()
+>         Right w8 -> unDrawByte w8
+>     return x
+-}
+peekByte :: (Monad m) => StateT (Producer ByteString m r) m (Either r Word8)
+peekByte = do
+    x <- drawByte
+    case x of
+        Left  _  -> return ()
+        Right w8 -> unDrawByte w8
+    return x
+{-# INLINABLE peekByte #-}
+
+{-| Check if the underlying 'Producer' has no more bytes
+
+    Note that this will skip over empty 'ByteString' chunks, unlike
+    'PP.isEndOfInput' from @pipes-parse@.
+
+> isEndOfBytes = liftM isLeft peekByte
+-}
+isEndOfBytes :: (Monad m) => StateT (Producer ByteString m r) m Bool
+isEndOfBytes = do
+    x <- peekByte
+    return (case x of
+        Left  _ -> True
+        Right _ -> False )
+{-# INLINABLE isEndOfBytes #-}
+
+{-| @(take n)@ only allows @n@ bytes to pass
+
+    Unlike @Pipes.ByteString.'Pipes.ByteString.take'@, this 'PP.unDraw's unused
+    bytes
+-}
+take :: (Monad m, Integral a) => a -> Pipe ByteString ByteString (StateT (Producer ByteString m r) m) ()
+take n0 = go n0 where
+    go n
+        | n <= 0 = return ()
+        | otherwise = do
+            bs <- await
+            let len = fromIntegral (BS.length bs)
+            if (len > n)
+                then do
+                    let n' = fromIntegral n
+                    lift . PP.unDraw $ unsafeDrop n' bs
+                    yield $ unsafeTake n' bs
+                else do
+                    yield bs
+                    go (n - len)
+{-# INLINABLE take #-}
+
+{-| Take bytes until they fail the predicate
+
+    Unlike @Pipes.ByteString.'Pipes.ByteString.takeWhile'@, this 'PP.unDraw's
+    unused bytes
+-}
+takeWhile
+    :: (Monad m)
+    => (Word8 -> Bool)
+    -> Pipe ByteString ByteString (StateT (Producer ByteString m r) m) ()
+takeWhile predicate = go
+  where
+    go = do
+        bs <- await
+        let (prefix, suffix) = BS.span predicate bs
+        if (BS.null suffix)
+            then do
+                yield bs
+                go
+            else do
+                lift $ PP.unDraw suffix
+                yield prefix
+{-# INLINABLE takeWhile #-}
