packages feed

cassava 0.2.1.2 → 0.2.2.0

raw patch · 10 files changed

+321/−149 lines, 10 filesdep +deepseqdep +lazy-csvPVP ok

version bump matches the API change (PVP)

Dependencies added: deepseq, lazy-csv

API changes (from Hackage documentation)

+ Data.Csv.Streaming: instance NFData a => NFData (Records a)

Files

Data/Csv.hs view
@@ -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 --
Data/Csv/Conversion.hs view
@@ -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))
Data/Csv/Encoding.hs view
@@ -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
Data/Csv/Incremental.hs view
@@ -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
Data/Csv/Parser.hs view
@@ -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
Data/Csv/Streaming.hs view
@@ -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'@.
Data/Csv/Types.hs view
@@ -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
Data/Csv/Util.hs view
@@ -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))
benchmarks/Benchmarks.hs view
@@ -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
cassava.cabal view
@@ -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