Cassava CSV Library for Haskell

repository·master·Indexed 42 days ago

https://github.com/haskell-hvr/cassava

A high-performance CSV parsing and encoding library for Haskell that balances RFC4180 compliance with flexibility. It provides functions like `decode` for headerless files and `decodeByName` for files with headers, emphasizing performance and a minimal dependency footprint.

Tokens
1.5K
Snippets
8
Records
9
Agent score
44%

What's inside Cassava

  1. Generate random CSV files for benchmarking

    master

    The benchmark suite includes a generator script Generate.hs to create randomized CSV files containing integer, double, and string columns. Using a seed ensures that benchmarks are reproducible.

    Usage: cabal run Generate.hs -- <rows> <columns> <seed>

    # Generate wide CSV (5000 rows, 500 columns, seed 42)
    cabal run Generate.hs -- 5000 500 42
    
    # Generate long CSV (1000000 rows, 10 columns, seed 42)
    cabal run Generate.hs -- 1000000 10 42
  2. Filter specific benchmarks by name

    master

    To avoid running the full suite, you can filter for specific benchmark groups using the -m (match) flag within --benchmark-options. This is useful for isolating specific scenarios like positional decoding or wide/long CSV performance.

    # Run only positional decode benchmarks
    cabal bench benchmark-iut --benchmark-options="-m prefix positional/decode"
    
    # Run only wide benchmarks
    cabal bench benchmark-iut --benchmark-options="-m prefix wide"
    
    # Run only long benchmarks
    cabal bench benchmark-iut --benchmark-options="-m prefix long"
  3. Run Cassava benchmarks

    master

    The suite provides two distinct benchmark targets to compare the local implementation against the official Hackage version:

    • benchmark-iut: Benchmarks the local cassava implementation (the implementation under test).
    • benchmark-ref: Benchmarks the reference cassava version from Hackage.

    Use cabal bench to execute them.

    # Run implementation-under-test benchmarks
    cabal bench benchmark-iut
    
    # Run reference benchmarks
    cabal bench benchmark-ref
  4. Generate HTML benchmark reports

    master

    You can export benchmark results to HTML files using the --benchmark-options flag to pass the --output argument to the underlying benchmarking engine.

    cabal bench benchmark-iut --benchmark-options="--output=report-iut.html"
    cabal bench benchmark-ref --benchmark-options="--output=report-ref.html"
  5. Parse CSV files without headers using `decode`

    master

    To parse a CSV file that does not contain a header row, use the decode function from Data.Csv. You must specify the header option (e.g., NoHeader) and provide the lazy ByteString data. The function returns either an error string or a Vector of records. For simple tuple-based records, you can use type annotations to guide the parser.

    {-# LANGUAGE ScopedTypeVariables #-}
    
    import qualified Data.ByteString.Lazy as BL
    import Data.Csv
    import qualified Data.Vector as V
    
    main :: IO ()
    main = do
        csvData <- BL.readFile "salaries.csv"
        case decode NoHeader csvData of
            Left err -> putStrLn err
            Right v -> V.forM_ v $ \ (name, salary :: Int) ->
                putStrLn $ name ++ " earns " ++ show salary ++ " dollars"
  6. Parse CSV files with headers using `decodeByName`

    master

    To parse a CSV file that includes a header row, use the decodeByName function. This requires your data type to implement the FromNamedRecord typeclass. You define how to map CSV column names to your data structure using the .: operator within the parseNamedRecord method. decodeByName returns a tuple containing the header and a Vector of the parsed records.

    {-# LANGUAGE OverloadedStrings #-}
    
    import Control.Applicative
    import qualified Data.ByteString.Lazy as BL
    import Data.Csv
    import qualified Data.Vector as V
    
    data Person = Person
        { name   :: !String
        , salary :: !Int
        }
    
    instance FromNamedRecord Person where
        parseNamedRecord r = Person <$> r .: "name" <*> r .: "salary"
    
    main :: IO ()
    main = do
        csvData <- BL.readFile "salaries.csv"
        case decodeByName csvData of
            Left err -> putStrLn err
            Right (_, v) -> V.forM_ v $ \ p ->
                putStrLn $ name p ++ " earns " ++ show (salary p) ++ " dollars"
  7. Reference the benchmark data files

    master

    The following CSV files are used as standard inputs for the benchmark suite:

    FileDescription
    presidents.csvUS Presidents data without header (positional parsing)
    presidents_with_header.csvUS Presidents data with header (named parsing)
    random_r5000_c500_s42.csvWide CSV: 5,000 rows × 500 columns
    random_r1000000_c10_s42.csvLong CSV: 1,000,000 rows × 10 columns