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 +9/−1
- Data/Csv/Conversion.hs +76/−49
- Data/Csv/Encoding.hs +77/−37
- Data/Csv/Incremental.hs +1/−6
- Data/Csv/Parser.hs +1/−6
- Data/Csv/Streaming.hs +17/−0
- Data/Csv/Types.hs +7/−0
- Data/Csv/Util.hs +11/−1
- benchmarks/Benchmarks.hs +71/−6
- cassava.cabal +51/−43
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