diff --git a/Data/Csv.hs b/Data/Csv.hs
--- a/Data/Csv.hs
+++ b/Data/Csv.hs
@@ -143,7 +143,15 @@
 -- >     Left err -> putStrLn err
 -- >     Right v  -> forM_ v $ \ (Hex val1, Hex val2) ->
 -- >         print (val1, val2)
-
+--
+-- You can use the unit type, @()@, to ignore a column. The
+-- 'parseField' method for @()@ doesn't look at the 'Field' and thus
+-- always decodes successfully. Note that it lacks a corresponding
+-- 'ToField' instance. Example:
+--
+-- > case decode False "foo,1\r\nbar,22" of
+-- >     Left  err -> putStrLn err
+-- >     Right v   -> forM_ v $ \ ((), i) -> print (i :: Int)
 
 -- $encoding
 --
diff --git a/Data/Csv/Conversion.hs b/Data/Csv/Conversion.hs
--- a/Data/Csv/Conversion.hs
+++ b/Data/Csv/Conversion.hs
@@ -22,6 +22,7 @@
     -- * Accessors
     , index
     , (.!)
+    , unsafeIndex
     , lookup
     , (.:)
     , namedField
@@ -63,6 +64,21 @@
 #endif
 
 ------------------------------------------------------------------------
+-- bytestring compatibility
+
+toStrict   :: L.ByteString -> B.ByteString
+fromStrict :: B.ByteString -> L.ByteString
+#if MIN_VERSION_bytestring(0,10,0)
+toStrict   = L.toStrict
+fromStrict = L.fromStrict
+#else
+toStrict   = B.concat . L.toChunks
+fromStrict = L.fromChunks . (:[])
+#endif
+{-# INLINE toStrict #-}
+{-# INLINE fromStrict #-}
+
+------------------------------------------------------------------------
 -- Type conversion
 
 ------------------------------------------------------------------------
@@ -82,7 +98,7 @@
 --
 -- here's an example type and instance:
 --
--- > data Person = Person { name :: Text, age :: Int }
+-- > data Person = Person { name :: !Text, age :: !Int }
 -- >
 -- > instance FromRecord Person where
 -- >     parseRecord v
@@ -109,11 +125,11 @@
 --
 -- An example type and instance:
 --
--- > data Person = Person { name :: Text, age :: Int }
+-- > data Person = Person { name :: !Text, age :: !Int }
 -- >
 -- > instance ToRecord Person where
--- >     toRecord (Person name age) = 'record' [
--- >         'toField' name, 'toField' age]
+-- >     toRecord (Person name age) = record [
+-- >         toField name, toField age]
 --
 -- Outputs data on this form:
 --
@@ -129,7 +145,7 @@
 
 instance FromField a => FromRecord (Only a) where
     parseRecord v
-        | n == 1    = Only <$> parseField (V.unsafeIndex v 0)
+        | n == 1    = Only <$> unsafeIndex v 0
         | otherwise = lengthMismatch 1 v
           where
             n = V.length v
@@ -141,8 +157,8 @@
 
 instance (FromField a, FromField b) => FromRecord (a, b) where
     parseRecord v
-        | n == 2    = (,) <$> parseField (V.unsafeIndex v 0)
-                          <*> parseField (V.unsafeIndex v 1)
+        | n == 2    = (,) <$> unsafeIndex v 0
+                          <*> unsafeIndex v 1
         | otherwise = lengthMismatch 2 v
           where
             n = V.length v
@@ -152,9 +168,9 @@
 
 instance (FromField a, FromField b, FromField c) => FromRecord (a, b, c) where
     parseRecord v
-        | n == 3    = (,,) <$> parseField (V.unsafeIndex v 0)
-                           <*> parseField (V.unsafeIndex v 1)
-                           <*> parseField (V.unsafeIndex v 2)
+        | n == 3    = (,,) <$> unsafeIndex v 0
+                           <*> unsafeIndex v 1
+                           <*> unsafeIndex v 2
         | otherwise = lengthMismatch 3 v
           where
             n = V.length v
@@ -166,10 +182,10 @@
 instance (FromField a, FromField b, FromField c, FromField d) =>
          FromRecord (a, b, c, d) where
     parseRecord v
-        | n == 4    = (,,,) <$> parseField (V.unsafeIndex v 0)
-                            <*> parseField (V.unsafeIndex v 1)
-                            <*> parseField (V.unsafeIndex v 2)
-                            <*> parseField (V.unsafeIndex v 3)
+        | n == 4    = (,,,) <$> unsafeIndex v 0
+                            <*> unsafeIndex v 1
+                            <*> unsafeIndex v 2
+                            <*> unsafeIndex v 3
         | otherwise = lengthMismatch 4 v
           where
             n = V.length v
@@ -182,11 +198,11 @@
 instance (FromField a, FromField b, FromField c, FromField d, FromField e) =>
          FromRecord (a, b, c, d, e) where
     parseRecord v
-        | n == 5    = (,,,,) <$> parseField (V.unsafeIndex v 0)
-                             <*> parseField (V.unsafeIndex v 1)
-                             <*> parseField (V.unsafeIndex v 2)
-                             <*> parseField (V.unsafeIndex v 3)
-                             <*> parseField (V.unsafeIndex v 4)
+        | n == 5    = (,,,,) <$> unsafeIndex v 0
+                             <*> unsafeIndex v 1
+                             <*> unsafeIndex v 2
+                             <*> unsafeIndex v 3
+                             <*> unsafeIndex v 4
         | otherwise = lengthMismatch 5 v
           where
             n = V.length v
@@ -200,12 +216,12 @@
           FromField f) =>
          FromRecord (a, b, c, d, e, f) where
     parseRecord v
-        | n == 6    = (,,,,,) <$> parseField (V.unsafeIndex v 0)
-                              <*> parseField (V.unsafeIndex v 1)
-                              <*> parseField (V.unsafeIndex v 2)
-                              <*> parseField (V.unsafeIndex v 3)
-                              <*> parseField (V.unsafeIndex v 4)
-                              <*> parseField (V.unsafeIndex v 5)
+        | n == 6    = (,,,,,) <$> unsafeIndex v 0
+                              <*> unsafeIndex v 1
+                              <*> unsafeIndex v 2
+                              <*> unsafeIndex v 3
+                              <*> unsafeIndex v 4
+                              <*> unsafeIndex v 5
         | otherwise = lengthMismatch 6 v
           where
             n = V.length v
@@ -219,13 +235,13 @@
           FromField f, FromField g) =>
          FromRecord (a, b, c, d, e, f, g) where
     parseRecord v
-        | n == 7    = (,,,,,,) <$> parseField (V.unsafeIndex v 0)
-                               <*> parseField (V.unsafeIndex v 1)
-                               <*> parseField (V.unsafeIndex v 2)
-                               <*> parseField (V.unsafeIndex v 3)
-                               <*> parseField (V.unsafeIndex v 4)
-                               <*> parseField (V.unsafeIndex v 5)
-                               <*> parseField (V.unsafeIndex v 6)
+        | n == 7    = (,,,,,,) <$> unsafeIndex v 0
+                               <*> unsafeIndex v 1
+                               <*> unsafeIndex v 2
+                               <*> unsafeIndex v 3
+                               <*> unsafeIndex v 4
+                               <*> unsafeIndex v 5
+                               <*> unsafeIndex v 6
         | otherwise = lengthMismatch 7 v
           where
             n = V.length v
@@ -286,7 +302,7 @@
 --
 -- > {-# LANGUAGE OverloadedStrings #-}
 -- >
--- > data Person = Person { name :: Text, age :: Int }
+-- > data Person = Person { name :: !Text, age :: !Int }
 -- >
 -- > instance FromRecord Person where
 -- >     parseNamedRecord m = Person <$>
@@ -307,7 +323,7 @@
 --
 -- An example type and instance:
 --
--- > data Person = Person { name :: Text, age :: Int }
+-- > data Person = Person { name :: !Text, age :: !Int }
 -- >
 -- > instance ToRecord Person where
 -- >     toNamedRecord (Person name age) = namedRecord [
@@ -386,12 +402,19 @@
     toField = maybe B.empty toField
     {-# INLINE toField #-}
 
+-- | Ignores the 'Field'. Always succeeds.
+instance FromField () where
+    parseField _ = pure ()
+    {-# INLINE parseField #-}
+
 -- | Assumes UTF-8 encoding.
 instance FromField Char where
-    parseField s
-        | T.compareLength t 1 == EQ = pure (T.head t)
-        | otherwise = typeError "Char" s Nothing
-      where t = T.decodeUtf8 s
+    parseField s =
+        case T.decodeUtf8' s of
+          Left e -> fail $ show e
+          Right t
+            | T.compareLength t 1 == EQ -> pure (T.head t)
+            | otherwise -> typeError "Char" s Nothing
     {-# INLINE parseField #-}
 
 -- | Uses UTF-8 encoding.
@@ -546,16 +569,16 @@
     {-# INLINE toField #-}
 
 instance FromField L.ByteString where
-    parseField s = pure (L.fromChunks [s])
+    parseField = pure . fromStrict
     {-# INLINE parseField #-}
 
 instance ToField L.ByteString where
-    toField = toField . B.concat . L.toChunks
+    toField = toStrict
     {-# INLINE toField #-}
 
--- | Assumes UTF-8 encoding.
+-- | Assumes UTF-8 encoding. Fails on invalid byte sequences.
 instance FromField T.Text where
-    parseField = pure . T.decodeUtf8
+    parseField = either (fail . show) pure . T.decodeUtf8'
     {-# INLINE parseField #-}
 
 -- | Uses UTF-8 encoding.
@@ -563,17 +586,17 @@
     toField = toField . T.encodeUtf8
     {-# INLINE toField #-}
 
--- | Assumes UTF-8 encoding.
+-- | Assumes UTF-8 encoding. Fails on invalid byte sequences.
 instance FromField LT.Text where
-    parseField s = pure (LT.fromChunks [T.decodeUtf8 s])
+    parseField = either (fail . show) (pure . LT.fromStrict) . T.decodeUtf8'
     {-# INLINE parseField #-}
 
 -- | Uses UTF-8 encoding.
 instance ToField LT.Text where
-    toField = toField . B.concat . L.toChunks . LT.encodeUtf8
+    toField = toField . toStrict . LT.encodeUtf8
     {-# INLINE toField #-}
 
--- | Assumes UTF-8 encoding.
+-- | Assumes UTF-8 encoding. Fails on invalid byte sequences.
 instance FromField [Char] where
     parseField = fmap T.unpack . parseField
     {-# INLINE parseField #-}
@@ -612,8 +635,7 @@
 --
 -- 'index' is a simple convenience function that is equivalent to
 -- @'parseField' (v '!' idx)@. If you're certain that the index is not
--- out of bounds, using @'parseField' (`V.unsafeIndex` v idx)@ is
--- somewhat faster.
+-- out of bounds, using 'unsafeIndex' is somewhat faster.
 index :: FromField a => Record -> Int -> Parser a
 index v idx = parseField (v ! idx)
 {-# INLINE index #-}
@@ -622,7 +644,13 @@
 (.!) :: FromField a => Record -> Int -> Parser a
 (.!) = index
 {-# INLINE (.!) #-}
+infixl 9 .!
 
+-- | Like 'index' but without bounds checking.
+unsafeIndex :: FromField a => Record -> Int -> Parser a
+unsafeIndex v idx = parseField (V.unsafeIndex v idx)
+{-# INLINE unsafeIndex #-}
+
 -- | Retrieve a field in the given record by name.  The result is
 -- 'empty' if the field is missing or if the value cannot be converted
 -- to the desired type.
@@ -754,7 +782,6 @@
 instance GFromRecordSum f NamedRecord => GFromNamedRecord (M1 i n f) where
     gparseNamedRecord v = 
         foldr (\f p -> p <|> M1 <$> f v) empty (IM.elems gparseRecordSum)
-
 
 class GFromRecordSum f r where
     gparseRecordSum :: IM.IntMap (r -> Parser (f p))
diff --git a/Data/Csv/Encoding.hs b/Data/Csv/Encoding.hs
--- a/Data/Csv/Encoding.hs
+++ b/Data/Csv/Encoding.hs
@@ -10,7 +10,7 @@
 --
 -- Encoding and decoding of data types into CSV.
 module Data.Csv.Encoding
-    (     
+    (
     -- * Encoding and decoding
       decode
     , decodeByName
@@ -31,7 +31,8 @@
 import Blaze.ByteString.Builder (Builder, fromByteString, fromWord8,
                                  toLazyByteString)
 import Blaze.ByteString.Builder.Char8 (fromString)
-import Control.Applicative ((*>), (<$>), (<*>), pure)
+import Control.Applicative ((*>), (<|>), optional, pure)
+import Data.Attoparsec.Char8 (endOfInput, endOfLine)
 import qualified Data.Attoparsec.ByteString.Lazy as AL
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Char8 as B8
@@ -45,10 +46,14 @@
 import Prelude hiding (unlines)
 
 import Data.Csv.Compat.Monoid ((<>))
-import Data.Csv.Conversion
-import Data.Csv.Parser
-import Data.Csv.Types
-import Data.Csv.Util ((<$!>))
+import Data.Csv.Conversion (FromNamedRecord, FromRecord, ToNamedRecord,
+                            ToRecord, parseNamedRecord, parseRecord, runParser,
+                            toNamedRecord, toRecord)
+import Data.Csv.Parser hiding (csv, csvWithHeader)
+import qualified Data.Csv.Parser as Parser
+import Data.Csv.Types hiding (toNamedRecord)
+import qualified Data.Csv.Types as Types
+import Data.Csv.Util (blankLine)
 
 -- TODO: 'encode' isn't as efficient as it could be.
 
@@ -97,22 +102,9 @@
                              -- skipped
            -> L.ByteString   -- ^ CSV data
            -> Either String (Vector a)
-decodeWith = decodeWithC (runParser . parseCsv)
+decodeWith = decodeWithC csv
 {-# INLINE [1] decodeWith #-}
 
-parseCsv :: FromRecord a => Csv -> Parser (Vector a)
-parseCsv xs = V.fromList <$!> mapM' parseRecord (V.toList xs)
-
-mapM' :: Monad m => (a -> m b) -> [a] -> m [b]
-mapM' f = go
-  where
-    go [] = return []
-    go (x:xs) = do
-        !y <- f x
-        ys <- go xs
-        return (y : ys)
-{-# INLINE mapM' #-}
-
 {-# RULES
     "idDecodeWith" decodeWith = idDecodeWith
  #-}
@@ -121,14 +113,17 @@
 -- conversion is performed.
 idDecodeWith :: DecodeOptions -> Bool -> L.ByteString
              -> Either String (Vector (Vector B.ByteString))
-idDecodeWith = decodeWithC pure
+idDecodeWith = decodeWithC Parser.csv
 
-decodeWithC :: (Csv -> Either String a) -> DecodeOptions -> Bool -> L.ByteString
-            -> Either String a
-decodeWithC convert !opts skipHeader = decodeWithP parser convert
+-- | Decode CSV data using the provided parser, skipping a leading
+-- header if 'skipHeader' is 'True'. Returns 'Left' @errMsg@ on
+-- failure.
+decodeWithC :: (DecodeOptions -> AL.Parser a) -> DecodeOptions -> Bool
+            -> BL8.ByteString -> Either String a
+decodeWithC p !opts skipHeader = decodeWithP parser
   where parser
-            | skipHeader = header (decDelimiter opts) *> csv opts
-            | otherwise  = csv opts
+            | skipHeader = header (decDelimiter opts) *> p opts
+            | otherwise  = p opts
 {-# INLINE decodeWithC #-}
 
 -- | Like 'decodeByName', but lets you customize how the CSV data is
@@ -137,12 +132,7 @@
                  => DecodeOptions  -- ^ Decoding options
                  -> L.ByteString   -- ^ CSV data
                  -> Either String (Header, Vector a)
-decodeByNameWith !opts =
-    decodeWithP (csvWithHeader opts)
-    (\ (hdr, vs) -> (,) <$> pure hdr <*> (runParser $ parseNamedCsv vs))
-
-parseNamedCsv :: FromNamedRecord a => Vector NamedRecord -> Parser (Vector a)
-parseNamedCsv xs = V.fromList <$!> mapM' parseNamedRecord (V.toList xs)
+decodeByNameWith !opts = decodeWithP (csvWithHeader opts)
 
 -- | Options that controls how data is encoded. These options can be
 -- used to e.g. encode data in a tab-separated format instead of in a
@@ -237,12 +227,62 @@
 prependToAll _   []     = []
 prependToAll sep (x:xs) = sep <> x : prependToAll sep xs
 
-decodeWithP :: AL.Parser a -> (a -> Either String b) -> L.ByteString -> Either String b
-decodeWithP p to s =
+decodeWithP :: AL.Parser a -> L.ByteString -> Either String a
+decodeWithP p s =
     case AL.parse p s of
-      AL.Done _ v     -> case to v of
-          Right a  -> Right a
-          Left msg -> Left $ "conversion error: " ++ msg
+      AL.Done _ v     -> Right v
       AL.Fail left _ msg -> Left $ "parse error (" ++ msg ++ ") at " ++
                             show (BL8.unpack left)
 {-# INLINE decodeWithP #-}
+
+-- These alternative implementation of the 'csv' and 'csvWithHeader'
+-- parsers from the 'Parser' module performs the
+-- 'FromRecord'/'FromNamedRecord' conversions on-the-fly, thereby
+-- avoiding the need to hold a big 'CSV' value in memory. The 'CSV'
+-- type has a quite large memory overhead due to high constant
+-- overheads of 'B.ByteString' and 'V.Vector'.
+
+-- TODO: Check that the error messages don't duplicate prefixes, as in
+-- "parse error: conversion error: ...".
+
+-- | Parse a CSV file that does not include a header.
+csv :: FromRecord a => DecodeOptions -> AL.Parser (V.Vector a)
+csv !opts = do
+    vals <- records
+    _ <- optional endOfLine
+    endOfInput
+    return $! V.fromList vals
+  where
+    records = do
+        !r <- record (decDelimiter opts)
+        if blankLine r
+            then (endOfLine *> records) <|> pure []
+            else case runParser (parseRecord r) of
+                Left msg  -> fail $ "conversion error: " ++ msg
+                Right val -> do
+                    !vals <- (endOfLine *> records) <|> pure []
+                    return (val : vals)
+{-# INLINE csv #-}
+
+-- | Parse a CSV file that includes a header.
+csvWithHeader :: FromNamedRecord a => DecodeOptions
+              -> AL.Parser (Header, V.Vector a)
+csvWithHeader !opts = do
+    !hdr <- header (decDelimiter opts)
+    vals <- records hdr
+    _ <- optional endOfLine
+    endOfInput
+    let !v = V.fromList vals
+    return (hdr, v)
+  where
+    records hdr = do
+        !r <- record (decDelimiter opts)
+        if blankLine r
+            then (endOfLine *> records hdr) <|> pure []
+            else case runParser (convert hdr r) of
+                Left msg  -> fail $ "conversion error: " ++ msg
+                Right val -> do
+                    !vals <- (endOfLine *> records hdr) <|> pure []
+                    return (val : vals)
+
+    convert hdr = parseNamedRecord . Types.toNamedRecord hdr
diff --git a/Data/Csv/Incremental.hs b/Data/Csv/Incremental.hs
--- a/Data/Csv/Incremental.hs
+++ b/Data/Csv/Incremental.hs
@@ -39,7 +39,6 @@
 import qualified Data.Attoparsec as A
 import Data.Attoparsec.Char8 (endOfInput, endOfLine)
 import qualified Data.ByteString as B
-import qualified Data.HashMap.Strict as HM
 import qualified Data.Vector as V
 
 import Data.Csv.Conversion hiding (Parser, record, toNamedRecord)
@@ -259,10 +258,6 @@
     go (DoneH hdr rest) =
         DoneH hdr (decodeWithP (parseNamedRecord . toNamedRecord hdr) opts rest)
 
--- Copied from Data.Csv.Parser
-toNamedRecord :: Header -> Record -> NamedRecord
-toNamedRecord hdr v = HM.fromList . V.toList $ V.zip hdr v
-
 ------------------------------------------------------------------------
 
 -- | Like 'decode', but lets you customize how the CSV data is parsed.
@@ -293,7 +288,7 @@
                 | B.null s  = Done (reverse acc'')
                 | otherwise = go Incomplete acc'' (parser s)
             acc' | blankLine r = acc
-                 | otherwise   = convert r : acc
+                 | otherwise   = let !r' = convert r in r' : acc
 
     parser = A.parse (record (decDelimiter opts) <* (endOfLine <|> endOfInput))
     convert = runParser . p
diff --git a/Data/Csv/Parser.hs b/Data/Csv/Parser.hs
--- a/Data/Csv/Parser.hs
+++ b/Data/Csv/Parser.hs
@@ -35,13 +35,12 @@
 import qualified Data.Attoparsec.Zepto as Z
 import qualified Data.ByteString as S
 import qualified Data.ByteString.Unsafe as S
-import qualified Data.HashMap.Strict as HM
 import Data.Monoid (mappend, mempty)
 import qualified Data.Vector as V
 import Data.Word (Word8)
 
 import Data.Csv.Types
-import Data.Csv.Util ((<$!>))
+import Data.Csv.Util ((<$!>), blankLine)
 
 -- | Options that controls how data is decoded. These options can be
 -- used to e.g. decode tab-separated data instead of comma-separated
@@ -103,9 +102,6 @@
     let !v = V.fromList vals
     return (hdr, v)
 
-toNamedRecord :: Header -> Record -> NamedRecord
-toNamedRecord hdr v = HM.fromList . V.toList $ V.zip hdr v
-
 -- | Parse a header, including the terminating line separator.
 header :: Word8  -- ^ Field delimiter
        -> AL.Parser Header
@@ -118,7 +114,6 @@
 
 removeBlankLines :: [Record] -> [Record]
 removeBlankLines = filter (not . blankLine)
-  where blankLine v = V.length v == 1 && (S.null (V.head v))
 
 -- | Parse a record, not including the terminating line separator. The
 -- terminating line separate is not included as the last record in a
diff --git a/Data/Csv/Streaming.hs b/Data/Csv/Streaming.hs
--- a/Data/Csv/Streaming.hs
+++ b/Data/Csv/Streaming.hs
@@ -28,6 +28,7 @@
     ) where
 
 import Control.Applicative ((<$>), (<*>), pure)
+import Control.DeepSeq (NFData(rnf))
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Lazy as BL
 import qualified Data.ByteString.Lazy.Char8 as BL8
@@ -42,6 +43,10 @@
 import Data.Csv.Parser
 import Data.Csv.Types
 
+#if !MIN_VERSION_bytestring(0,10,0)
+import qualified Data.ByteString.Lazy.Internal as BL  -- for constructors
+#endif
+
 -- $example
 --
 -- A short usage example:
@@ -115,6 +120,18 @@
       where
         traverseElem (Left err) = pure $ Left err
         traverseElem (Right y)  = Right <$> f y
+
+instance NFData a => NFData (Records a) where
+    rnf (Cons r rs) = rnf r `seq` rnf rs
+#if MIN_VERSION_bytestring(0,10,0)
+    rnf (Nil errMsg rest) = rnf errMsg `seq` rnf rest
+#else
+    rnf (Nil errMsg rest) = rnf errMsg `seq` rnfLazyByteString rest
+
+rnfLazyByteString :: BL.ByteString -> ()
+rnfLazyByteString BL.Empty       = ()
+rnfLazyByteString (BL.Chunk _ b) = rnfLazyByteString b
+#endif
 
 -- | Efficiently deserialize CSV records in a streaming fashion.
 -- Equivalent to @'decodeWith' 'defaultDecodeOptions'@.
diff --git a/Data/Csv/Types.hs b/Data/Csv/Types.hs
--- a/Data/Csv/Types.hs
+++ b/Data/Csv/Types.hs
@@ -7,11 +7,13 @@
     , Name
     , NamedRecord
     , Field
+    , toNamedRecord
     ) where
 
 import qualified Data.ByteString as S
 import qualified Data.HashMap.Strict as HM
 import Data.Vector (Vector)
+import qualified Data.Vector as V
 
 -- | CSV data represented as a Haskell vector of vector of
 -- bytestrings.
@@ -34,3 +36,8 @@
 
 -- | A single field within a record.
 type Field = S.ByteString
+
+-- | Convert a 'Record' to a 'NamedRecord' by attaching column names.
+-- The 'Header' and 'Record' must be of the same length.
+toNamedRecord :: Header -> Record -> NamedRecord
+toNamedRecord hdr v = HM.fromList . V.toList $ V.zip hdr v
diff --git a/Data/Csv/Util.hs b/Data/Csv/Util.hs
--- a/Data/Csv/Util.hs
+++ b/Data/Csv/Util.hs
@@ -1,5 +1,11 @@
-module Data.Csv.Util ((<$!>)) where
+module Data.Csv.Util
+      ((<$!>)
+    , blankLine
+    ) where
 
+import qualified Data.ByteString as B
+import qualified Data.Vector as V
+
 -- | A strict version of 'Data.Functor.<$>' for monads.
 (<$!>) :: Monad m => (a -> b) -> m a -> m b
 f <$!> m = do
@@ -8,3 +14,7 @@
 {-# INLINE (<$!>) #-}
 
 infixl 4 <$!>
+
+-- | Is this an empty record (i.e. a blank line)?
+blankLine :: V.Vector B.ByteString -> Bool
+blankLine v = V.length v == 1 && (B.null (V.head v))
diff --git a/benchmarks/Benchmarks.hs b/benchmarks/Benchmarks.hs
--- a/benchmarks/Benchmarks.hs
+++ b/benchmarks/Benchmarks.hs
@@ -1,22 +1,64 @@
 {-# LANGUAGE BangPatterns, FlexibleInstances, OverloadedStrings,
-             TypeSynonymInstances #-}
+             RecordWildCards, TypeSynonymInstances #-}
+{-# OPTIONS_GHC -funbox-strict-fields #-}
+
 module Main ( main ) where
 
 import Control.Applicative
+import Control.DeepSeq
 import Criterion.Main
 import Data.ByteString (ByteString)
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Lazy as BL
 import qualified Data.HashMap.Strict as HM
+import Control.Monad (mzero)
 import Data.Text (Text)
+import qualified Text.CSV.Lazy.ByteString as LazyCsv
 import Data.Vector (Vector)
+import qualified Data.Vector as V
 
 import Data.Csv
+import qualified Data.Csv.Streaming as Streaming
 
-type President = (Int, Text, ByteString, ByteString, ByteString, Text, Text)
+data President = President
+                 { presidency     :: !Int
+                 , president      :: !Text
+                 , wikipediaEntry :: !ByteString
+                 , tookOffice     :: !ByteString
+                 , leftOffice     :: !ByteString
+                 , party          :: !Text
+                 , homeState      :: !Text
+                 }
 
+instance NFData President where
+    rnf (President {}) = ()
+
+instance FromRecord President where
+    parseRecord v
+        | V.length v == 7 = President <$>
+                            v .!! 0 <*>
+                            v .!! 1 <*>
+                            v .!! 2 <*>
+                            v .!! 3 <*>
+                            v .!! 4 <*>
+                            v .!! 5 <*>
+                            v .!! 6
+        | otherwise       = mzero
+
+-- | Unchecked version of '(.!)'.
+(.!!) :: FromField a => Record -> Int -> Parser a
+v .!! idx = parseField (V.unsafeIndex v idx)
+{-# INLINE (.!!) #-}
+infixl 9 .!!
+
+instance ToRecord President where
+    toRecord (President {..}) =
+        record [toField presidency, toField president, toField wikipediaEntry,
+                toField tookOffice, toField leftOffice, toField party,
+                toField homeState]
+
 instance FromNamedRecord President where
-    parseNamedRecord m = (,,,,,,) <$>
+    parseNamedRecord m = President <$>
                          m .: "Presidency" <*>
                          m .: "President" <*>
                          m .: "Wikipedia Entry" <*>
@@ -26,8 +68,7 @@
                          m .: "Home State"
 
 instance ToNamedRecord President where
-    toNamedRecord (presidency, president, wikipediaEntry, tookOffice,
-                   leftOffice, party, homeState) = namedRecord
+    toNamedRecord (President {..}) = namedRecord
         [ "Presidency"      .= presidency
         , "President"       .= president
         , "Wikipedia Entry" .= wikipediaEntry
@@ -42,6 +83,17 @@
 
 type BSHashMap a = HM.HashMap B.ByteString a
 
+instance NFData LazyCsv.CSVField where
+    rnf LazyCsv.CSVField {} = ()
+    rnf LazyCsv.CSVFieldError {} = ()
+
+instance NFData LazyCsv.CSVError where
+    rnf (LazyCsv.IncorrectRow !_ !_ !_ xs) = rnf xs
+    rnf (LazyCsv.BlankLine _ _ _ field)    = rnf field
+    rnf (LazyCsv.FieldError field)         = rnf field
+    rnf (LazyCsv.DuplicateHeader _ s)      = rnf s
+    rnf LazyCsv.NoData                     = ()
+
 main :: IO ()
 main = do
     !csvData <- fromStrict `fmap` B.readFile "benchmarks/presidents.csv"
@@ -54,6 +106,10 @@
           [ bgroup "decode"
             [ bench "presidents/without conversion" $ whnf idDecode csvData
             , bench "presidents/with conversion" $ whnf decodePresidents csvData
+            , bgroup "streaming"
+              [ bench "presidents/without conversion" $ nf idDecodeS csvData
+              , bench "presidents/with conversion" $ nf decodePresidentsS csvData
+              ]
             ]
           , bgroup "encode"
             [ bench "presidents/with conversion" $ whnf encode presidents
@@ -61,13 +117,16 @@
           ]
         , bgroup "named"
           [ bgroup "decode"
-            [  bench "presidents/without conversion" $ whnf idDecodeN csvDataN
+            [ bench "presidents/without conversion" $ whnf idDecodeN csvDataN
             , bench "presidents/with conversion" $ whnf decodePresidentsN csvDataN
             ]
           , bgroup "encode"
             [ bench "presidents/with conversion" $ whnf (encodeByName hdr) presidentsN
             ]
           ]
+        , bgroup "comparison"
+          [ bench "lazy-csv" $ nf LazyCsv.parseCSV csvData
+          ]
         ]
   where
     decodePresidents :: BL.ByteString -> Either String (Vector President)
@@ -76,8 +135,14 @@
     decodePresidentsN :: BL.ByteString -> Either String (Header, Vector President)
     decodePresidentsN = decodeByName
 
+    decodePresidentsS :: BL.ByteString -> Streaming.Records President
+    decodePresidentsS = Streaming.decode False
+
     idDecode :: BL.ByteString -> Either String (Vector (Vector B.ByteString))
     idDecode = decode False
 
     idDecodeN :: BL.ByteString -> Either String (Header, Vector (BSHashMap B.ByteString))
     idDecodeN = decodeByName
+
+    idDecodeS :: BL.ByteString -> Streaming.Records (Vector B.ByteString)
+    idDecodeS = Streaming.decode False
diff --git a/cassava.cabal b/cassava.cabal
--- a/cassava.cabal
+++ b/cassava.cabal
@@ -1,5 +1,5 @@
 Name:                cassava
-Version:             0.2.1.2
+Version:             0.2.2.0
 Synopsis:            A CSV parsing and encoding library
 Description:
   A CSV parsing and encoding library optimized for ease of use and high
@@ -20,63 +20,71 @@
 
 
 Library
-  Exposed-modules:     Data.Csv
-                       Data.Csv.Incremental
-                       Data.Csv.Parser
-                       Data.Csv.Streaming
+  Exposed-modules:
+    Data.Csv
+    Data.Csv.Incremental
+    Data.Csv.Parser
+    Data.Csv.Streaming
 
-  Other-modules:       Data.Csv.Compat.Monoid
-                       Data.Csv.Conversion
-                       Data.Csv.Conversion.Internal
-                       Data.Csv.Encoding
-                       Data.Csv.Types
-                       Data.Csv.Util
+  Other-modules:
+    Data.Csv.Compat.Monoid
+    Data.Csv.Conversion
+    Data.Csv.Conversion.Internal
+    Data.Csv.Encoding
+    Data.Csv.Types
+    Data.Csv.Util
 
-  Build-depends:       array < 0.5,
-                       attoparsec >= 0.10.2 && < 0.11,
-                       base < 5,
-                       blaze-builder < 0.4,
-                       bytestring < 0.11,
-                       containers < 0.6,
-                       text < 0.12,
-                       unordered-containers < 0.3,
-                       vector < 0.11
+  Build-depends:
+    array < 0.5,
+    attoparsec >= 0.10.2 && < 0.11,
+    base < 5,
+    blaze-builder < 0.4,
+    bytestring < 0.11,
+    containers < 0.6,
+    deepseq < 1.4,
+    text < 0.12,
+    unordered-containers < 0.3,
+    vector < 0.11
 
-  ghc-options:         -Wall -O2
+  ghc-options: -Wall -O2
 
   if impl(ghc >= 7.2.1)
-    cpp-options:       -DGENERICS
-    build-depends:     ghc-prim >= 0.2 && < 0.4
+    cpp-options: -DGENERICS
+    build-depends: ghc-prim >= 0.2 && < 0.4
   
 Test-suite unit-tests
   Type: exitcode-stdio-1.0
   Main-is: UnitTests.hs
-  Build-depends:       attoparsec,
-                       base,
-                       bytestring,
-                       cassava,
-                       HUnit,
-                       QuickCheck >= 2.0,
-                       test-framework,
-                       test-framework-hunit,
-                       test-framework-quickcheck2,
-                       text,
-                       unordered-containers,
-                       vector
+  Build-depends:
+    attoparsec,
+    base,
+    bytestring,
+    cassava,
+    HUnit,
+    QuickCheck >= 2.0,
+    test-framework,
+    test-framework-hunit,
+    test-framework-quickcheck2,
+    text,
+    unordered-containers,
+    vector
 
   hs-source-dirs: tests
-  ghc-options:         -Wall
+  ghc-options: -Wall
 
 Benchmark benchmarks
   Type: exitcode-stdio-1.0
   Main-is: Benchmarks.hs
-  Build-depends:       base,
-                       bytestring,
-                       cassava,
-                       criterion,
-                       text,
-                       unordered-containers,
-                       vector
+  Build-depends:
+    base,
+    bytestring,
+    cassava,
+    criterion,
+    deepseq,
+    lazy-csv,
+    text,
+    unordered-containers,
+    vector
   hs-source-dirs: benchmarks
 
 source-repository head
