I'm reading a CSV using Haskell. I'm not sure if this is the appropriate way to do it.
This is what I'm doing:
- Read rows from a CSV -> return lazy byte string
- Parse the headers and rows from the CSV to a tuple -> (headers, [Stock])
- Remove the headers -> [Stock]
- Filter the stocks that are "Common Stock" -> [Stock]
- Print the resulting stocks
Any feedback on how to write better Haskell code is appreciated!
The code is a Stack project, you can find the project and instructions on how to run it: here
I read this section of Stephen Diehl's guide before writing the code: What I wish I knew when learning Haskell
Here is the code to read the CSV file. The main function is printStocks
.
{-# LANGUAGE OverloadedStrings #-}
module Lib (printStocks) where
import Control.Monad
import qualified Data.ByteString.Lazy as BL
import Data.Csv
import qualified Data.Vector as V
-- data type to model a stock
data Stock = Stock
{ code :: String,
name :: String,
country :: String,
exchange :: String,
currency :: String,
instrumentType :: String
}
deriving (Show)
instance FromNamedRecord Stock where
parseNamedRecord record =
Stock
<$> record .: "Code"
<*> record .: "Name"
<*> record .: "Country"
<*> record .: "Exchange"
<*> record .: "Currency"
<*> record .: "Type"
-- type synonyms to handle the CSV contents
type ErrorMsg = String
type CsvData = (Header, V.Vector Stock)
-- Function to read the CSV
parseCSV :: FilePath -> IO (Either ErrorMsg CsvData)
parseCSV filePath = do
contents <- BL.readFile filePath
return $ decodeByName contents
-- Discard headers from CsvData
removeHeaders :: CsvData -> V.Vector Stock
removeHeaders = snd
-- Check if the given element is a Common Stock
isStock :: Stock -> Bool
isStock stock = instrumentType stock == "Common Stock"
filterStocks :: V.Vector Stock -> V.Vector Stock
filterStocks = V.filter isStock
-- Print the stocks from the CSV file
printStocks :: FilePath -> IO ()
printStocks filePath =
parseCSV filePath
>>= print . fmap (filterStocks . removeHeaders)