diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2012, 2013 Gabriel Gonzalez
+Copyright (c) 2012-2014 Gabriel Gonzalez
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without modification,
diff --git a/pipes-bytestring.cabal b/pipes-bytestring.cabal
--- a/pipes-bytestring.cabal
+++ b/pipes-bytestring.cabal
@@ -1,10 +1,10 @@
 Name: pipes-bytestring
-Version: 1.0.3
+Version: 2.0.0
 Cabal-Version: >=1.8.0.2
 Build-Type: Simple
 License: BSD3
 License-File: LICENSE
-Copyright: 2012, 2013 Gabriel Gonzalez
+Copyright: 2012-2014 Gabriel Gonzalez
 Author: Gabriel Gonzalez
 Maintainer: Gabriel439@gmail.com
 Bug-Reports: https://github.com/Gabriel439/Haskell-Pipes-ByteString-Library/issues
@@ -20,10 +20,10 @@
     Build-Depends:
         base         >= 4       && < 5   ,
         bytestring   >= 0.9.2.1 && < 0.11,
-        pipes        >= 4.0     && < 4.1 ,
-        pipes-parse  >= 2.0.0   && < 2.1 ,
+        pipes        >= 4.0     && < 4.2 ,
+        pipes-group  >= 1.0.0   && < 1.1 ,
+        pipes-parse  >= 3.0.0   && < 3.1 ,
+        profunctors  >= 3.1.1   && < 4.1 ,
         transformers >= 0.2.0.0 && < 0.4
-    Exposed-Modules:
-        Pipes.ByteString,
-        Pipes.ByteString.Parse
+    Exposed-Modules: Pipes.ByteString
     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
@@ -22,11 +22,11 @@
 >     runEffect $ P.fromHandle hIn >-> P.toHandle hOut
 
     You can stream to and from 'stdin' and 'stdout' using the predefined 'stdin'
-    and 'stdout' proxies, like in the following \"echo\" program:
+    and 'stdout' pipes, like in the following \"echo\" program:
 
 > main = runEffect $ P.stdin >-> P.stdout
 
-    You can also translate pure lazy 'BL.ByteString's to and from proxies:
+    You can also translate pure lazy 'BL.ByteString's to and from pipes:
 
 > import qualified Data.ByteString.Lazy.Char8 as BL
 >
@@ -37,13 +37,12 @@
     example, to stream only the first three lines of 'stdin' to 'stdout' you
     would write:
 
+> import Lens.Family (over)
 > import Pipes
 > import qualified Pipes.ByteString as PB
-> import qualified Pipes.Parse      as PP
+> import Pipes.Parse (takes)
 >
-> main = runEffect $ takeLines 3 PB.stdin >-> PB.stdout
->   where
->     takeLines n = PB.unlines . PP.takeFree n . PB.lines
+> main = runEffect $ over PB.lines (takes 3) PB.stdin >-> PB.stdout
 
     The above program will never bring more than one chunk (~ 32 KB) into
     memory, no matter how long the lines are.
@@ -62,7 +61,6 @@
     , fromHandle
     , hGetSome
     , hGet
-    , pack
 
     -- * Servers
     , hGetSomeN
@@ -83,12 +81,11 @@
     , elemIndices
     , findIndices
     , scan
-    , unpack
 
     -- * Folds
     , toLazy
     , toLazyM
-    , fold
+    , foldBytes
     , head
     , last
     , null
@@ -105,45 +102,50 @@
     , findIndex
     , count
 
-    -- * Splitters
+    -- * Parsing
+    -- $parse
+    , nextByte
+    , drawByte
+    , unDrawByte
+    , peekByte
+    , isEndOfBytes
+
+    -- * Parsing Lenses
     , splitAt
-    , chunksOf
     , span
     , break
-    , splitWith
-    , split
     , groupBy
     , group
-    , lines
-    , words
+    , word
+    , line
 
-    -- * Transformations
+    -- * Transforming Byte Streams
     , intersperse
-
-    -- * Joiners
-    , intercalate
-    , unlines
-    , unwords
+    , pack
+    , chunksOf'
 
-    -- * Low-level Parsers
-    -- $parse
-    , nextByte
-    , drawByte
-    , unDrawByte
-    , peekByte
-    , isEndOfBytes
-    , takeWhile'
+    -- * FreeT Splitters
+    , chunksOf
+    , splitsWith
+    , splits
+    , groupsBy
+    , groups
+    , lines
+    , words
 
     -- * Re-exports
     -- $reexports
     , module Data.ByteString
+    , module Data.Profunctor
     , module Data.Word
+    , module Pipes.Group
     , module Pipes.Parse
     ) where
 
+import Control.Applicative ((<*))
 import Control.Exception (throwIO, try)
-import Control.Monad (liftM)
-import Control.Monad.Trans.State.Strict (StateT)
+import Control.Monad (liftM, join)
+import Control.Monad.Trans.State.Strict (modify)
 import qualified Data.ByteString as BS
 import Data.ByteString (ByteString)
 import Data.ByteString.Internal (isSpaceWord8)
@@ -151,26 +153,26 @@
 import Data.ByteString.Lazy.Internal (foldrChunks, defaultChunkSize)
 import Data.ByteString.Unsafe (unsafeTake, unsafeDrop)
 import Data.Char (ord)
+import Data.Functor.Constant (Constant(Constant, getConstant))
 import Data.Functor.Identity (Identity)
+import Data.Profunctor (Profunctor)
+import qualified Data.Profunctor
 import qualified Data.List as List
 import Data.Word (Word8)
 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.Group as PG
+import Pipes.Group (concats, intercalates, FreeT)
 import qualified Pipes.Parse as PP
-import Pipes.Parse (input, concat, FreeT, isEndOfInput)
+import Pipes.Parse (Parser)
 import qualified Pipes.Prelude as P
 import qualified System.IO as IO
 import Prelude hiding (
       all
     , any
     , break
-    , concat
     , concatMap
     , drop
     , dropWhile
@@ -189,13 +191,11 @@
     , 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 ()
+fromLazy :: Monad m => BL.ByteString -> Producer' ByteString m ()
 fromLazy bs = foldrChunks (\e a -> yield e >> a) (return ()) bs
 {-# INLINABLE fromLazy #-}
 
@@ -244,18 +244,8 @@
                 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 #-}
+(^.) :: a -> ((b -> Constant b b) -> (a -> Constant b a)) -> b
+a ^. lens = getConstant (lens Constant a)
 
 {-| Like 'hGetSome', except you can vary the maximum chunk size for each request
 -}
@@ -287,9 +277,6 @@
 {-| Stream bytes to 'stdout'
 
     Unlike 'toHandle', 'stdout' gracefully terminates on a broken output pipe.
-
-    Note: For best performance, use @(for source (liftIO . putStr))@ instead of
-    @(source >-> stdout)@.
 -}
 stdout :: MonadIO m => Consumer' ByteString m ()
 stdout = go
@@ -308,8 +295,7 @@
 
 {-| Convert a byte stream into a 'Handle'
 
-    Note: For best performance, use @(for source (liftIO . hPutStr handle))@
-    instead of @(source >-> toHandle handle)@.
+> p >-> toHandle handle = for p (liftIO . hPutStr handle)
 -}
 toHandle :: MonadIO m => IO.Handle -> Consumer' ByteString m r
 toHandle h = for cat (liftIO . BS.hPut h)
@@ -320,13 +306,13 @@
   #-}
 
 -- | Apply a transformation to each 'Word8' in the stream
-map :: (Monad m) => (Word8 -> Word8) -> Pipe ByteString ByteString m r
+map :: Monad m => (Word8 -> Word8) -> Pipe ByteString ByteString m r
 map f = P.map (BS.map f)
 {-# INLINABLE map #-}
 
 -- | Map a function over the byte stream and concatenate the results
 concatMap
-    :: (Monad m) => (Word8 -> ByteString) -> Pipe ByteString ByteString m r
+    :: Monad m => (Word8 -> ByteString) -> Pipe ByteString ByteString m r
 concatMap f = P.map (BS.concatMap f)
 {-# INLINABLE concatMap #-}
 
@@ -361,7 +347,7 @@
 {-# INLINABLE drop #-}
 
 -- | Take bytes until they fail the predicate
-takeWhile :: (Monad m) => (Word8 -> Bool) -> Pipe ByteString ByteString m ()
+takeWhile :: Monad m => (Word8 -> Bool) -> Pipe ByteString ByteString m ()
 takeWhile predicate = go
   where
     go = do
@@ -375,7 +361,7 @@
 {-# INLINABLE takeWhile #-}
 
 -- | Drop bytes until they fail the predicate
-dropWhile :: (Monad m) => (Word8 -> Bool) -> Pipe ByteString ByteString m r
+dropWhile :: Monad m => (Word8 -> Bool) -> Pipe ByteString ByteString m r
 dropWhile predicate = go where
     go = do
         bs <- await
@@ -387,7 +373,7 @@
 {-# INLINABLE dropWhile #-}
 
 -- | Only allows 'Word8's to pass if they satisfy the predicate
-filter :: (Monad m) => (Word8 -> Bool) -> Pipe ByteString ByteString m r
+filter :: Monad m => (Word8 -> Bool) -> Pipe ByteString ByteString m r
 filter predicate = P.map (BS.filter predicate)
 {-# INLINABLE filter #-}
 
@@ -408,7 +394,7 @@
 
 -- | Strict left scan over the bytes
 scan
-    :: (Monad m)
+    :: Monad m
     => (Word8 -> Word8 -> Word8) -> Word8 -> Pipe ByteString ByteString m r
 scan step begin = go begin
   where
@@ -420,16 +406,6 @@
         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'
 -}
@@ -444,19 +420,23 @@
     simple testing purposes.  Idiomatic @pipes@ style consumes the chunks
     immediately as they are generated instead of loading them all into memory.
 -}
-toLazyM :: (Monad m) => Producer ByteString m () -> m BL.ByteString
+toLazyM :: Monad m => Producer ByteString m () -> m BL.ByteString
 toLazyM = liftM BL.fromChunks . P.toListM
 {-# INLINABLE toLazyM #-}
 
--- | Reduce the stream of bytes using a strict left fold
-fold
+{-| Reduce the stream of bytes using a strict left fold
+
+    Note: It's more efficient to use folds from @Control.Foldl.ByteString@ in
+    conjunction with @Pipes.Prelude.'Pipes.Prelude.fold'@ when possible
+-}
+foldBytes
     :: Monad m
     => (x -> Word8 -> x) -> x -> (x -> r) -> Producer ByteString m () -> m r
-fold step begin done = P.fold (\x bs -> BS.foldl' step x bs) begin done
-{-# INLINABLE fold #-}
+foldBytes step begin done = P.fold (\x bs -> BS.foldl' step x bs) begin done
+{-# INLINABLE foldBytes #-}
 
 -- | Retrieve the first 'Word8'
-head :: (Monad m) => Producer ByteString m () -> m (Maybe Word8)
+head :: Monad m => Producer ByteString m () -> m (Maybe Word8)
 head = go
   where
     go p = do
@@ -467,7 +447,7 @@
 {-# INLINABLE head #-}
 
 -- | Retrieve the last 'Word8'
-last :: (Monad m) => Producer ByteString m () -> m (Maybe Word8)
+last :: Monad m => Producer ByteString m () -> m (Maybe Word8)
 last = go Nothing
   where
     go r p = do
@@ -481,7 +461,7 @@
 {-# INLINABLE last #-}
 
 -- | Determine if the stream is empty
-null :: (Monad m) => Producer ByteString m () -> m Bool
+null :: Monad m => Producer ByteString m () -> m Bool
 null = P.all BS.null
 {-# INLINABLE null #-}
 
@@ -491,17 +471,17 @@
 {-# INLINABLE length #-}
 
 -- | Fold that returns whether 'M.Any' received 'Word8's satisfy the predicate
-any :: (Monad m) => (Word8 -> Bool) -> Producer ByteString m () -> m Bool
+any :: Monad m => (Word8 -> Bool) -> Producer ByteString m () -> m Bool
 any predicate = P.any (BS.any predicate)
 {-# INLINABLE any #-}
 
 -- | Fold that returns whether 'M.All' received 'Word8's satisfy the predicate
-all :: (Monad m) => (Word8 -> Bool) -> Producer ByteString m () -> m Bool
+all :: Monad m => (Word8 -> Bool) -> Producer ByteString m () -> m Bool
 all predicate = P.all (BS.all predicate)
 {-# INLINABLE all #-}
 
 -- | Return the maximum 'Word8' within a byte stream
-maximum :: (Monad m) => Producer ByteString m () -> m (Maybe Word8)
+maximum :: Monad m => Producer ByteString m () -> m (Maybe Word8)
 maximum = P.fold step Nothing id
   where
     step mw8 bs =
@@ -513,7 +493,7 @@
 {-# INLINABLE maximum #-}
 
 -- | Return the minimum 'Word8' within a byte stream
-minimum :: (Monad m) => Producer ByteString m () -> m (Maybe Word8)
+minimum :: Monad m => Producer ByteString m () -> m (Maybe Word8)
 minimum = P.fold step Nothing id
   where
     step mw8 bs =
@@ -525,20 +505,20 @@
 {-# INLINABLE minimum #-}
 
 -- | Determine whether any element in the byte stream matches the given 'Word8'
-elem :: (Monad m) => Word8 -> Producer ByteString m () -> m Bool
+elem :: Monad m => Word8 -> Producer ByteString m () -> m Bool
 elem w8 = P.any (BS.elem w8)
 {-# INLINABLE elem #-}
 
 {-| Determine whether all elements in the byte stream do not match the given
     'Word8'
 -}
-notElem :: (Monad m) => Word8 -> Producer ByteString m () -> m Bool
+notElem :: Monad m => Word8 -> Producer ByteString m () -> m Bool
 notElem w8 = P.all (BS.notElem w8)
 {-# INLINABLE notElem #-}
 
 -- | Find the first element in the stream that matches the predicate
 find
-    :: (Monad m)
+    :: Monad m
     => (Word8 -> Bool) -> Producer ByteString m () -> m (Maybe Word8)
 find predicate p = head (p >-> filter predicate)
 {-# INLINABLE find #-}
@@ -568,55 +548,126 @@
 count w8 p = P.fold (+) 0 id (p >-> P.map (fromIntegral . BS.count w8))
 {-# INLINABLE count #-}
 
--- | Splits a 'Producer' after the given number of bytes
-splitAt
-    :: (Monad m, Integral n)
-    => n
-    -> Producer ByteString m r
-    -> Producer' ByteString m (Producer ByteString m r)
-splitAt = go
+{-| 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 0 p = return p
-    go n p = do
-        x <- lift (next p)
+    go p = do
+        x <- next p
         case x of
-            Left   r       -> return (return r)
-            Right (bs, p') -> do
-                let len = fromIntegral (BS.length bs)
-                if (len <= n)
-                    then do
-                        yield bs
-                        go (n - len) p'
-                    else do
-                        let (prefix, suffix) = BS.splitAt (fromIntegral n) bs
-                        yield prefix
-                        return (yield suffix >> p')
-{-# INLINABLE splitAt #-}
+            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 #-}
 
--- | Split a byte stream into 'FreeT'-delimited byte streams of fixed size
-chunksOf
+{-| Draw one 'Word8' from the underlying 'Producer', returning 'Nothing' if the
+    'Producer' is empty
+-}
+drawByte :: Monad m => Parser ByteString m (Maybe Word8)
+drawByte = do
+    x <- PP.draw
+    case x of
+        Nothing -> return Nothing
+        Just bs -> case (BS.uncons bs) of
+            Nothing        -> drawByte
+            Just (w8, bs') -> do
+                PP.unDraw bs'
+                return (Just w8)
+{-# INLINABLE drawByte #-}
+
+-- | Push back a 'Word8' onto the underlying 'Producer'
+unDrawByte :: Monad m => Word8 -> Parser ByteString 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
+>         Nothing -> return ()
+>         Just w8 -> unDrawByte w8
+>     return x
+-}
+peekByte :: Monad m => Parser ByteString m (Maybe Word8)
+peekByte = do
+    x <- drawByte
+    case x of
+        Nothing -> return ()
+        Just 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
+    'Pipes.Parse.isEndOfInput' from @pipes-parse@.
+
+> isEndOfBytes = liftM isNothing peekByte
+-}
+isEndOfBytes :: Monad m => Parser ByteString m Bool
+isEndOfBytes = do
+    x <- peekByte
+    return (case x of
+        Nothing -> True
+        Just _  -> False )
+{-# INLINABLE isEndOfBytes #-}
+
+type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a)
+
+type Iso' a b = forall f p . (Functor f, Profunctor p) => p b (f b) -> p a (f a)
+
+-- | Improper lens that splits a 'Producer' after the given number of bytes
+splitAt
     :: (Monad m, Integral n)
-    => n -> Producer ByteString m r -> FreeT (Producer ByteString m) m r
-chunksOf n = go
+    => n
+    -> Lens' (Producer ByteString m x)
+             (Producer ByteString m (Producer ByteString m x))
+splitAt n0 k p0 = fmap join (k (go n0 p0))
   where
-    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 (go p'')
-{-# INLINABLE chunksOf #-}
+    -- go  :: (Monad m, Integral n)
+    --     => n
+    --     -> Producer ByteString m r
+    --     -> Producer' ByteString m (Producer ByteString m r)
+    go n p =
+        if (n <= 0)
+        then return p
+	else do
+            x <- lift (next p)
+            case x of
+                Left   r       -> return (return r)
+                Right (bs, p') -> do
+                    let len = fromIntegral (BS.length bs)
+                    if (len <= n)
+                        then do
+                            yield bs
+                            go (n - len) p'
+                        else do
+                            let (prefix, suffix) =
+                                    BS.splitAt (fromIntegral n) bs
+                            yield prefix
+                            return (yield suffix >> p')
+{-# INLINABLE splitAt #-}
 
-{-| Split a byte stream in two, where the first byte stream is the longest
-    consecutive group of bytes that satisfy the predicate
+{-| Improper lens that splits after the longest consecutive group of bytes that
+    satisfy the given predicate
 -}
 span
-    :: (Monad m)
+    :: Monad m
     => (Word8 -> Bool)
-    -> Producer  ByteString m  r
-    -> Producer' ByteString m (Producer ByteString m r)
-span predicate = go
+    -> Lens' (Producer ByteString m x)
+             (Producer ByteString m (Producer ByteString m x))
+span predicate k p0 = fmap join (k (go p0))
   where
     go p = do
         x <- lift (next p)
@@ -633,129 +684,93 @@
                         return (yield suffix >> p')
 {-# INLINABLE span #-}
 
-{-| Split a byte stream in two, where the first byte stream is the longest
-    consecutive group of bytes that don't satisfy the predicate
+{-| Improper lens that splits after the longest consecutive group of bytes that
+    fail the given predicate
 -}
 break
-    :: (Monad m)
+    :: Monad m
     => (Word8 -> Bool)
-    -> Producer ByteString m  r
-    -> Producer ByteString m (Producer ByteString m r)
+    -> Lens' (Producer ByteString m x)
+             (Producer ByteString m (Producer ByteString m x))
 break predicate = span (not . predicate)
 {-# INLINABLE break #-}
 
-{-| Split a byte stream into sub-streams delimited by bytes that satisfy the
-    predicate
--}
-splitWith
-    :: (Monad m)
-    => (Word8 -> Bool)
-    -> Producer ByteString m r
-    -> PP.FreeT (Producer ByteString m) m r
-splitWith predicate p0 = PP.FreeT (go0 p0)
-  where
-    go0 p = do
-        x <- next p
-        case x of
-            Left   r       -> return (PP.Pure r)
-            Right (bs, p') ->
-                if (BS.null bs)
-                then go0 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
-split :: (Monad m)
-      => Word8
-      -> Producer ByteString m r
-      -> FreeT (Producer ByteString m) m r
-split w8 = splitWith (w8 ==)
-{-# INLINABLE split #-}
-
-{-| Group a byte stream into 'FreeT'-delimited byte streams using the supplied
-    equality predicate
+{-| Improper lens that splits after the first group of matching bytes, as
+    defined by the given equality predicate
 -}
 groupBy
-    :: (Monad m)
+    :: Monad m
     => (Word8 -> Word8 -> Bool)
-    -> Producer ByteString m r
-    -> FreeT (Producer ByteString m) m r
-groupBy equal p0 = PP.FreeT (go p0)
+    -> Lens' (Producer ByteString m r)
+             (Producer ByteString m (Producer ByteString m r))
+groupBy equals k p0 = fmap join (k (_groupBy p0))
   where
-    go p = do
-        x <- next p
+    -- _groupBy
+    --     :: Monad m
+    --     => Producer ByteString m r
+    --     -> Producer ByteString m (Producer ByteString m r)
+    _groupBy p = do
+        x <- lift (next p)
         case x of
-            Left   r       -> return (PP.Pure r)
+            Left   r       -> return (return r)
             Right (bs, p') -> case (BS.uncons bs) of
-                Nothing      -> go p'
-                Just (w8, _) -> do
-                    return $ PP.Free $ do
-                        p'' <- span (equal w8) (yield bs >> p')
-                        return $ PP.FreeT (go p'')
+                Nothing      -> _groupBy p'
+                Just (w8, _) -> (yield bs >> p')^.span (equals w8)
 {-# INLINABLE groupBy #-}
 
--- | Group a byte stream into 'FreeT'-delimited byte streams of identical bytes
+-- | Like 'groupBy', where the equality predicate is ('==')
 group
-    :: (Monad m) => Producer ByteString m r -> FreeT (Producer ByteString m) m r
+    :: Monad m
+    => Lens' (Producer ByteString m r)
+             (Producer ByteString m (Producer ByteString m r))
 group = groupBy (==)
 {-# INLINABLE group #-}
 
-{-| Split a byte stream into 'FreeT'-delimited lines
+{-| Improper lens that splits a 'Producer' after the first word
 
+    Unlike 'words', this does not drop leading whitespace
+
     Note: This function is purely for demonstration purposes since it assumes a
     particular encoding.  You should prefer the 'Data.Text.Text' equivalent of
     this function from the upcoming @pipes-text@ library.
 -}
-lines
-    :: (Monad m) => Producer ByteString m r -> FreeT (Producer ByteString m) m r
-lines p0 = PP.FreeT (go0 p0)
+word
+    :: Monad m
+    => Lens' (Producer ByteString m r)
+             (Producer ByteString m (Producer ByteString m r))
+word k p0 = fmap join (k (to p0))
   where
-    go0 p = do
-        x <- next p
-        case x of
-            Left   r       -> return (PP.Pure r)
-            Right (bs, p') ->
-                if (BS.null bs)
-                then go0 p'
-                else return $ PP.Free $ go1 (yield bs >> p')
-    go1 p = do
-        p' <- break (fromIntegral (ord '\n') ==) p
-        return $ PP.FreeT $ do
-            x  <- nextByte p'
-            case x of
-                Left   r       -> return (PP.Pure r)
-                Right (_, p'') -> go0 p''
-{-# INLINABLE lines #-}
+    -- to
+    --     :: Monad m
+    --     => Producer ByteString m r
+    --     -> Producer ByteString m (Producer ByteString m r)
+    to p = do
+        p' <- p^.span isSpaceWord8
+        p'^.break isSpaceWord8
+{-# INLINABLE word #-}
 
-{-| Split a byte stream into 'FreeT'-delimited words
+nl :: Word8
+nl = fromIntegral (ord '\n')
 
+{-| Improper lens that splits a 'Producer' after the first line
+
+    Unlike 'lines', this does not consume the newline marker, which is stored
+    within the inner 'Producer'
+
     Note: This function is purely for demonstration purposes since it assumes a
     particular encoding.  You should prefer the 'Data.Text.Text' equivalent of
     this function from the upcoming @pipes-text@ library.
 -}
-words
-    :: (Monad m) => Producer ByteString m r -> FreeT (Producer ByteString m) m r
-words = go
-  where
-    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 #-}
+line
+    :: Monad m
+    => Lens' (Producer ByteString m r)
+             (Producer ByteString m (Producer ByteString m r))
+line = break (== nl)
+{-# INLINABLE line #-}
 
 -- | Intersperse a 'Word8' in between the bytes of the byte stream
 intersperse
-    :: (Monad m) => Word8 -> Producer ByteString m r -> Producer ByteString m r
+    :: Monad m => Word8 -> Producer ByteString m r -> Producer ByteString m r
 intersperse w8 = go0
   where
     go0 p = do
@@ -775,88 +790,207 @@
                 go1 p'
 {-# INLINABLE intersperse #-}
 
-{-| 'intercalate' concatenates the 'FreeT'-delimited byte streams after
-    interspersing a byte stream in between them
+-- | Improper isomorphism between a 'Producer' of 'ByteString's and 'Word8's
+pack :: Monad m => Iso' (Producer Word8 m x) (Producer ByteString m x)
+pack = Data.Profunctor.dimap to (fmap from)
+  where
+    -- to :: Monad m => Producer Word8 m x -> Producer ByteString m x
+    to p = PG.folds step id done (p^.PG.chunksOf defaultChunkSize)
+
+    step diffAs w8 = diffAs . (w8:)
+
+    done diffAs = BS.pack (diffAs [])
+
+    -- from :: Monad m => Producer ByteString m x -> Producer Word8 m x
+    from p = for p (each . BS.unpack)
+{-# INLINABLE pack #-}
+
+{-| Group byte stream chunks into chunks of fixed length
+
+    Note: This is the /only/ function in this API that concatenates
+    'ByteString' chunks, which requires allocating new `ByteString`s
 -}
-intercalate
-    :: (Monad m)
-    => Producer ByteString m ()
-    -> FreeT (Producer ByteString m) m r
-    -> Producer ByteString m r
-intercalate p0 = go0
+chunksOf'
+    :: (Monad m, Integral n)
+    => n -> Producer ByteString m r -> Producer ByteString m r
+chunksOf' n p =
+    PG.folds
+        (\diffBs bs -> diffBs . (bs:))
+        id
+        (\diffBs -> BS.concat (diffBs []))
+        (p ^. chunksOf n)
+{-# INLINABLE chunksOf' #-}
+
+-- | Split a byte stream into 'FreeT'-delimited byte streams of fixed size
+chunksOf
+    :: (Monad m, Integral n)
+    => n
+    -> Lens' (Producer ByteString m x) (FreeT (Producer ByteString m) m x)
+chunksOf n k p0 = fmap concats (k (go p0))
   where
-    go0 f = do
-        x <- lift (PP.runFreeT f)
-        case x of
-            PP.Pure r -> return r
-            PP.Free p -> do
-                f' <- p
-                go1 f'
-    go1 f = do
-        x <- lift (PP.runFreeT f)
+    go p = PG.FreeT $ do
+        x <- next p
+        return $ case x of
+            Left   r       -> PG.Pure r
+            Right (bs, p') -> PG.Free $ do
+                p'' <- (yield bs >> p')^.splitAt n
+                return (go p'')
+{-# INLINABLE chunksOf #-}
+
+{-| Split a byte stream into groups separated by bytes that satisfy the
+    predicate
+-}
+splitsWith
+    :: Monad m
+    => (Word8 -> Bool)
+    -> Producer ByteString m x -> FreeT (Producer ByteString m) m x
+splitsWith predicate p0 = PG.FreeT (go0 p0)
+  where
+    go0 p = do
+        x <- next p
         case x of
-            PP.Pure r -> return r
-            PP.Free p -> do
-                p0
-                f' <- p
-                go1 f'
-{-# INLINABLE intercalate #-}
+            Left   r       -> return (PG.Pure r)
+            Right (bs, p') ->
+                if (BS.null bs)
+                then go0 p'
+                else go1 (yield bs >> p')
+    go1 p = return $ PG.Free $ do
+        p' <- p^.break predicate
+        return $ PG.FreeT $ do
+            x <- nextByte p'
+            case x of
+                Left   r       -> return (PG.Pure r)
+                Right (_, p'') -> go1 p''
+{-# INLINABLE splitsWith #-}
 
-{-| Join 'FreeT'-delimited lines into a byte stream
+-- | Split a byte stream into groups separated by the given byte
+splits
+    :: Monad m
+    => Word8
+    -> Lens' (Producer ByteString m x) (FreeT (Producer ByteString m) m x)
+splits w8 k p =
+    fmap (PG.intercalates (yield (BS.singleton w8))) (k (splitsWith (w8 ==) p))
+{-# INLINABLE splits #-}
 
+{-| Isomorphism between a byte stream and groups of identical bytes using the
+    supplied equality predicate
+-}
+groupsBy
+    :: Monad m
+    => (Word8 -> Word8 -> Bool)
+    -> Lens' (Producer ByteString m x) (FreeT (Producer ByteString m) m x)
+groupsBy equals k p0 = fmap concats (k (_groupsBy p0))
+  where
+    -- _groupsBy
+    --     :: Monad m
+    --     => (Word8 -> Word8 -> Bool)
+    --     -> Producer ByteString m x
+    --     -> FreeT (Producer ByteString m) m x
+    _groupsBy p0' = PG.FreeT (go p0')
+      where
+        go p = do
+            x <- next p
+            case x of
+                Left   r       -> return (PG.Pure r)
+                Right (bs, p') -> case (BS.uncons bs) of
+                    Nothing      -> go p'
+                    Just (w8, _) -> do
+                        return $ PG.Free $ do
+                            p'' <- (yield bs >> p')^.span (equals w8)
+                            return $ PG.FreeT (go p'')
+{-# INLINABLE groupsBy #-}
+
+-- | Like 'groupsBy', where the equality predicate is ('==')
+groups
+    :: Monad m
+    => Lens' (Producer ByteString m x) (FreeT (Producer ByteString m) m x)
+groups = groupsBy (==)
+{-# INLINABLE groups #-}
+
+{-| Improper isomorphism between a bytestream and its lines
+
     Note: This function is purely for demonstration purposes since it assumes a
     particular encoding.  You should prefer the 'Data.Text.Text' equivalent of
     this function from the upcoming @pipes-text@ library.
 -}
-unlines
-    :: (Monad m) => FreeT (Producer ByteString m) m r -> Producer ByteString m r
-unlines = go
+lines
+    :: Monad m
+    => Iso' (Producer ByteString m x) (FreeT (Producer ByteString m) m x)
+lines = Data.Profunctor.dimap _lines (fmap _unlines)
   where
-    go f = do
-        x <- lift (PP.runFreeT f)
-        case x of
-            PP.Pure r -> return r
-            PP.Free p -> do
-                f' <- p
-                yield $ BS.singleton $ fromIntegral (ord '\n')
-                go f'
-{-# INLINABLE unlines #-}
+    -- _lines
+    --     :: Monad m
+    --     => Producer ByteString m x -> FreeT (Producer ByteString m) m x
+    _lines p0 = PG.FreeT (go0 p0)
+      where
+        go0 p = do
+            x <- next p
+            case x of
+                Left   r       -> return (PG.Pure r)
+                Right (bs, p') ->
+                    if (BS.null bs)
+                    then go0 p'
+                    else return $ PG.Free $ go1 (yield bs >> p')
+        go1 p = do
+            p' <- p^.line
+            return $ PG.FreeT $ do
+                x  <- nextByte p'
+                case x of
+                    Left   r       -> return (PG.Pure r)
+                    Right (_, p'') -> go0 p''
 
-{-| Join 'FreeT'-delimited words into a byte stream
+    -- _unlines
+    --     :: Monad m
+    --      => FreeT (Producer ByteString m) m x -> Producer ByteString m x
+    _unlines = concats . PG.maps addNewline
 
+    -- addNewline
+    --     :: Monad m => Producer ByteString m r -> Producer ByteString m r
+    addNewline p = p <* yield (BS.singleton nl)
+{-# INLINABLE lines #-}
+
+{-| Improper isomorphism between a bytestream and its words
+
     Note: This function is purely for demonstration purposes since it assumes a
     particular encoding.  You should prefer the 'Data.Text.Text' equivalent of
     this function from the upcoming @pipes-text@ library.
 -}
-unwords
-    :: (Monad m) => FreeT (Producer ByteString m) m r -> Producer ByteString m r
-unwords = intercalate (yield $ BS.singleton $ fromIntegral $ ord ' ')
-{-# INLINABLE unwords #-}
+words
+    :: Monad m
+    => Iso' (Producer ByteString m x) (FreeT (Producer ByteString m) m x)
+words = Data.Profunctor.dimap _words (fmap _unwords)
+  where
+    -- _words
+    --     :: Monad m
+    --     => Producer ByteString m x -> FreeT (Producer ByteString m) m x
+    _words p = PG.FreeT $ do
+            x <- next (p >-> dropWhile isSpaceWord8)
+            return $ case x of
+                Left   r       -> PG.Pure r
+                Right (bs, p') -> PG.Free $ do
+                    p'' <- (yield bs >> p')^.break isSpaceWord8
+                    return (_words p'')
 
+    -- _unwords
+    --     :: Monad m
+    --     => FreeT (Producer ByteString m) m x -> Producer ByteString m x
+    _unwords = PG.intercalates (yield $ BS.singleton $ fromIntegral $ ord ' ')
+{-# INLINABLE words #-}
+
 {- $parse
     The following parsing utilities are single-byte analogs of the ones found
     in @pipes-parse@.
 -}
 
-{-| Take bytes until they fail the predicate
-
-    Unlike 'takeWhile', this 'PP.unDraw's unused bytes
--}
-takeWhile'
-    :: (Monad m)
-    => (Word8 -> Bool)
-    -> Pipe ByteString ByteString (StateT (Producer ByteString m r) m) ()
-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.Profunctor@ re-exports the 'Profunctor' type.
+
     @Data.Word@ re-exports the 'Word8' type.
 
-    @Pipes.Parse@ re-exports 'input', 'concat', and 'FreeT' (the type).
+    @Pipes.Parse@ re-exports 'Parser'.
+
+    @Pipes.Group@ re-exports 'concats', 'intercalates', and 'FreeT'
+    (the type).
 -}
diff --git a/src/Pipes/ByteString/Parse.hs b/src/Pipes/ByteString/Parse.hs
deleted file mode 100644
--- a/src/Pipes/ByteString/Parse.hs
+++ /dev/null
@@ -1,142 +0,0 @@
--- | 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 #-}
