{-# LANGUAGE DerivingVia #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE UndecidableInstances #-} ----------------------------------------------------------------------------- -- | -- Module : $module -- Copyright : (c) Laurent P. René de Cotret -- License : MIT -- Maintainer : laurent.decotret@outlook.com -- Portability : portable -- -- This module contains functions to serialize/deserialize 'Series' -- to/from bytes. module Data.Series.IO ( -- * Deserialize 'Series' readCSV, readCSVFromFile, -- * Serialize 'Series' writeCSV, writeCSVToFile, ) where import Control.Monad.IO.Class ( MonadIO ) import qualified Data.ByteString.Lazy as BL import Data.Csv ( FromNamedRecord(..), ToNamedRecord(..), ) import Data.Series ( Series ) import qualified Data.Series.Generic.IO as Generic.IO {-| Read a comma-separated value (CSV) bytestream into a series. Consider the following bytestream read from a file: @ latitude,longitude,city 48.856667,2.352222,Paris 40.712778,-74.006111,New York City 25.0375,121.5625,Taipei -34.603333,-58.381667,Buenos Aires @ We want to get a series of the latitude an longitude, indexed by the column "city". First, we need to do is to create a datatype representing the latitude and longitude information, and our index: @ data LatLong = MkLatLong { latitude :: Double , longitude :: Double } deriving ( Show ) newtype City = MkCity String deriving ( Eq, Ord, Show ) @ Second, we need to create an instance of `Data.Csv.FromNamedRecord` for our new types: @ import "Data.Csv" ( 'FromNamedRecord', '(.:)' ) instance 'FromNamedRecord' LatLong where 'parseNamedRecord' r = MkLatLong \<$\> r .: "latitude" \<*\> r .: "longitude" instance 'FromNamedRecord' City where 'parseNamedRecord' r = MkCity \<$\> r .: "city" @ Finally, we're ready to read our stream: @ import "Data.Series" import "Data.Series.IO" main :: IO () main = do let fp = "path/to/my/file.csv" let (latlongs :: 'Series' City LatLong) = either (error . show) id \<$\> `readCSVFromFile` fp print latlongs @ -} readCSV :: (Ord k, FromNamedRecord k, FromNamedRecord a) => BL.ByteString -> Either String (Series k a) readCSV :: forall k a. (Ord k, FromNamedRecord k, FromNamedRecord a) => ByteString -> Either String (Series k a) readCSV = ByteString -> Either String (Series Vector k a) forall (v :: * -> *) a k. (Vector v a, Ord k, FromNamedRecord k, FromNamedRecord a) => ByteString -> Either String (Series v k a) Generic.IO.readCSV {-| This is a helper function to read a CSV directly from a filepath. See the documentation for 'readCSV' on how to prepare your types. Then, for example, you can use 'readCSVFromFile' as: @ import "Data.Series" import "Data.Series.IO" main :: IO () main = do let fp = "path/to/my/file.csv" let (latlongs :: 'Series' City LatLong) = either (error . show) id \<$\> `readCSVFromFile` fp print latlongs @ -} readCSVFromFile :: (MonadIO m, Ord k, FromNamedRecord k, FromNamedRecord a) => FilePath -> m (Either String (Series k a)) readCSVFromFile :: forall (m :: * -> *) k a. (MonadIO m, Ord k, FromNamedRecord k, FromNamedRecord a) => String -> m (Either String (Series k a)) readCSVFromFile = String -> m (Either String (Series Vector k a)) forall (m :: * -> *) (v :: * -> *) a k. (MonadIO m, Vector v a, Ord k, FromNamedRecord k, FromNamedRecord a) => String -> m (Either String (Series v k a)) Generic.IO.readCSVFromFile {-| Read a comma-separated value (CSV) bytestream into a series. Consider the following bytestream read from a file: @ latitude,longitude,city 48.856667,2.352222,Paris 40.712778,-74.006111,New York City 25.0375,121.5625,Taipei -34.603333,-58.381667,Buenos Aires @ We want to get a series of the latitude an longitude, indexed by the column "city". First, we need to do is to create a datatype representing the latitude and longitude information, and our index: @ data LatLong = MkLatLong { latitude :: Double , longitude :: Double } deriving ( Show ) newtype City = MkCity String deriving ( Eq, Ord, Show ) @ Second, we need to create an instance of `Data.Csv.FromNamedRecord` for our new types: @ import "Data.Csv" ( 'FromNamedRecord', '(.:)' ) instance 'FromNamedRecord' LatLong where 'parseNamedRecord' r = MkLatLong \<$\> r .: "latitude" \<*\> r .: "longitude" instance 'FromNamedRecord' City where 'parseNamedRecord' r = MkCity \<$\> r .: "city" @ Finally, we're ready to read our stream: @ import Data.Series import Data.Series.IO main :: IO () main = do stream <- (...) -- Read the bytestring from somewhere let (latlongs :: 'Series' City LatLong) = either (error . show) id \<$\> `readCSV` stream print latlongs @ -} writeCSV :: (ToNamedRecord k, ToNamedRecord a) => Series k a -> BL.ByteString writeCSV :: forall k a. (ToNamedRecord k, ToNamedRecord a) => Series k a -> ByteString writeCSV = Series Vector k a -> ByteString forall (v :: * -> *) a k. (Vector v a, ToNamedRecord k, ToNamedRecord a) => Series v k a -> ByteString Generic.IO.writeCSV -- | This is a helper function to write a 'Series' directly to CSV. -- See the documentation for 'writeCSV' on how to prepare your types. writeCSVToFile :: (MonadIO m, ToNamedRecord k, ToNamedRecord a) => FilePath -> Series k a -> m () writeCSVToFile :: forall (m :: * -> *) k a. (MonadIO m, ToNamedRecord k, ToNamedRecord a) => String -> Series k a -> m () writeCSVToFile = String -> Series Vector k a -> m () forall (m :: * -> *) (v :: * -> *) a k. (MonadIO m, Vector v a, ToNamedRecord k, ToNamedRecord a) => String -> Series v k a -> m () Generic.IO.writeCSVToFile