packages feed

Frames 0.6.0 → 0.6.1

raw patch · 8 files changed

+154/−84 lines, 8 filesdep ~vinylPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: vinyl

API changes (from Hackage documentation)

- Frames.ColumnUniverse: instance (Data.Vinyl.Core.RPureConstrained GHC.Show.Show ts, Data.Vinyl.Core.RecApplicative ts) => GHC.Show.Show (Data.Vinyl.CoRec.CoRec Frames.ColumnUniverse.ColInfo ts)
+ Frames: infixr 9 :.
+ Frames: pattern Col :: KnownSymbol s => t -> ElField '(s, t)
+ Frames: type family RecordColumns t
+ Frames.Col: pattern Col :: KnownSymbol s => t -> ElField '(s, t)
+ Frames.Col: type family ReplaceColumns x ys
+ Frames.ColumnTypeable: instance Frames.ColumnTypeable.Parseable GHC.Int.Int32
+ Frames.ColumnTypeable: instance Frames.ColumnTypeable.Parseable GHC.Int.Int64
+ Frames.ColumnTypeable: instance Frames.ColumnTypeable.Parseable GHC.Integer.Type.Integer
+ Frames.ColumnTypeable: parseIntish :: (Readable a, MonadPlus f) => Text -> f (Parsed a)
+ Frames.InCore: frameChunks :: Int -> FrameRec rs -> [FrameRec rs]
+ Frames.InCore: produceFrameChunks :: forall rs m. (RecVec rs, PrimMonad m) => Int -> Producer (Record rs) m () -> Producer (FrameRec rs) m ()
+ Frames.InCore: type family Vectors rs
+ Frames.Melt: type family RDeleteAll ss ts
+ Frames.Rec: type family RecordColumns t
+ Frames.RecF: type family ColFun f x
+ Frames.TypeLevel: type family ReplaceAllSnd a (xs :: [(k1, k2)])
- Frames: (&:) :: KnownSymbol s => a -> Record rs -> Record (s :-> a : rs)
+ Frames: (&:) :: KnownSymbol s => a -> Record rs -> Record ((s :-> a) : rs)
- Frames: (<+>) :: () => Rec f as -> Rec f bs -> Rec f as ++ bs
+ Frames: (<+>) :: () => Rec f as -> Rec f bs -> Rec f (as ++ bs)
- Frames: class (MonadCatch m, MonadMask m, MonadIO m, MonadIO Base m) => MonadSafe (m :: * -> *)
+ Frames: class (MonadCatch m, MonadMask m, MonadIO m, MonadIO Base m) => MonadSafe (m :: Type -> Type)
- Frames: data SafeT (m :: * -> *) r
+ Frames: data SafeT (m :: Type -> Type) r
- Frames: data Rec (a :: u -> *) (b :: [u]) :: forall u. () => u -> * -> [u] -> *
+ Frames: data Rec (a :: u -> Type) (b :: [u]) :: forall u. () => u -> Type -> [u] -> Type
- Frames: onCoRec :: RPureConstrained c ts => forall (a :: k2). (a ∈ ts, c a) => f a -> g b -> CoRec f ts -> g b
+ Frames: onCoRec :: RPureConstrained c ts => (forall (a :: k2). (a ∈ ts, c a) => f a -> g b) -> CoRec f ts -> g b
- Frames: onField :: RPureConstrained c ts => forall a. (a ∈ ts, c a) => a -> b -> Field ts -> b
+ Frames: onField :: RPureConstrained c ts => (forall a. (a ∈ ts, c a) => a -> b) -> Field ts -> b
- Frames: recUncons :: Record (s :-> a : rs) -> (a, Record rs)
+ Frames: recUncons :: Record ((s :-> a) : rs) -> (a, Record rs)
- Frames: rfield :: Functor f => a -> f b -> ElField (,) s a -> f ElField (,) s b
+ Frames: rfield :: Functor f => (a -> f b) -> ElField ((,) s a) -> f (ElField ((,) s b))
- Frames: rsubset :: (RecSubset record rs ss is, Functor g, RecSubsetFCtx record f) => record f rs -> g record f rs -> record f ss -> g record f ss
+ Frames: rsubset :: (RecSubset record rs ss is, Functor g, RecSubsetFCtx record f) => (record f rs -> g (record f rs)) -> record f ss -> g (record f ss)
- Frames: runSafeP :: (MonadMask m, MonadIO m) => Effect SafeT m r -> Effect' m r
+ Frames: runSafeP :: (MonadMask m, MonadIO m) => Effect (SafeT m) r -> Effect' m r
- Frames.Col: Col' :: (ElField (s :-> a)) -> Col' s a
+ Frames.Col: Col' :: ElField (s :-> a) -> Col' s a
- Frames.ColumnUniverse: data CoRec (a :: k -> *) (b :: [k]) :: forall k. () => k -> * -> [k] -> *
+ Frames.ColumnUniverse: data CoRec (a :: k -> Type) (b :: [k]) :: forall k. () => k -> Type -> [k] -> Type
- Frames.Exploration: select :: (fs ⊆ rs) => proxy fs -> Record rs -> Record fs
+ Frames.Exploration: select :: fs ⊆ rs => proxy fs -> Record rs -> Record fs
- Frames.Frame: Frame :: !Int -> Int -> r -> Frame r
+ Frames.Frame: Frame :: !Int -> (Int -> r) -> Frame r
- Frames.InCore: inCoreSoA :: forall m rs. (PrimMonad m, RecVec rs) => Producer (Record rs) m () -> m (Int, Rec (((->) Int) :. ElField) rs)
+ Frames.InCore: inCoreSoA :: forall m rs. (PrimMonad m, RecVec rs) => Producer (Record rs) m () -> m (Int, Rec ((->) Int :. ElField) rs)
- Frames.InCore: produceRec :: RecVec rs => proxy rs -> Record (Vectors rs) -> Rec (((->) Int) :. ElField) rs
+ Frames.InCore: produceRec :: RecVec rs => proxy rs -> Record (Vectors rs) -> Rec ((->) Int :. ElField) rs
- Frames.Melt: meltAux :: forall vs ss ts. (vs ⊆ ts, ss ⊆ ts, Disjoint ss ts ~ 'True, ts ≅ (vs ++ ss), ColumnHeaders vs, RowToColumn vs vs) => Record ts -> [Record ("value" :-> CoRec ElField vs : ss)]
+ Frames.Melt: meltAux :: forall vs ss ts. (vs ⊆ ts, ss ⊆ ts, Disjoint ss ts ~ 'True, ts ≅ (vs ++ ss), ColumnHeaders vs, RowToColumn vs vs) => Record ts -> [Record (("value" :-> CoRec ElField vs) : ss)]
- Frames.Melt: meltRow' :: forall proxy vs ts ss. (vs ⊆ ts, ss ⊆ ts, vs ~ RDeleteAll ss ts, Disjoint ss ts ~ 'True, ts ≅ (vs ++ ss), ColumnHeaders vs, RowToColumn vs vs) => proxy ss -> Record ts -> [Record ("value" :-> CoRec ElField vs : ss)]
+ Frames.Melt: meltRow' :: forall proxy vs ts ss. (vs ⊆ ts, ss ⊆ ts, vs ~ RDeleteAll ss ts, Disjoint ss ts ~ 'True, ts ≅ (vs ++ ss), ColumnHeaders vs, RowToColumn vs vs) => proxy ss -> Record ts -> [Record (("value" :-> CoRec ElField vs) : ss)]
- Frames.Rec: (&:) :: KnownSymbol s => a -> Record rs -> Record (s :-> a : rs)
+ Frames.Rec: (&:) :: KnownSymbol s => a -> Record rs -> Record ((s :-> a) : rs)
- Frames.Rec: recUncons :: Record (s :-> a : rs) -> (a, Record rs)
+ Frames.Rec: recUncons :: Record ((s :-> a) : rs) -> (a, Record rs)
- Frames.RecF: rappend :: () => Rec f as -> Rec f bs -> Rec f as ++ bs
+ Frames.RecF: rappend :: () => Rec f as -> Rec f bs -> Rec f (as ++ bs)
- Frames.RecF: rtraverse :: Applicative h => forall (x :: u). () => f x -> h g x -> Rec f rs -> h Rec g rs
+ Frames.RecF: rtraverse :: Applicative h => (forall (x :: u). () => f x -> h (g x)) -> Rec f rs -> h (Rec g rs)
- Frames.RecF: stripNames :: StripFieldNames ts => Rec ElField ts -> Rec Identity Unlabeled ts
+ Frames.RecF: stripNames :: StripFieldNames ts => Rec ElField ts -> Rec Identity (Unlabeled ts)
- Frames.RecF: stripNames' :: (StripFieldNames ts, Functor f) => Rec f :. ElField ts -> Rec f Unlabeled ts
+ Frames.RecF: stripNames' :: (StripFieldNames ts, Functor f) => Rec (f :. ElField) ts -> Rec f (Unlabeled ts)
- Frames.RecF: withNames :: StripFieldNames ts => Rec Identity Unlabeled ts -> Rec ElField ts
+ Frames.RecF: withNames :: StripFieldNames ts => Rec Identity (Unlabeled ts) -> Rec ElField ts
- Frames.RecF: withNames' :: (StripFieldNames ts, Functor f) => Rec f Unlabeled ts -> Rec f :. ElField ts
+ Frames.RecF: withNames' :: (StripFieldNames ts, Functor f) => Rec f (Unlabeled ts) -> Rec (f :. ElField) ts
- Frames.TH: RowGen :: [String] -> String -> Separator -> String -> Proxy a -> Separator -> Producer [Text] (SafeT IO) () -> RowGen
+ Frames.TH: RowGen :: [String] -> String -> Separator -> String -> Proxy a -> (Separator -> Producer [Text] (SafeT IO) ()) -> RowGen
- Frames.TH: colDec :: Text -> String -> Text -> (Either (String -> Q [Dec]) Type) -> Q (Type, [Dec])
+ Frames.TH: colDec :: Text -> String -> Text -> Either (String -> Q [Dec]) Type -> Q (Type, [Dec])

Files

Frames.cabal view
@@ -1,5 +1,5 @@ name:                Frames-version:             0.6.0+version:             0.6.1 synopsis:            Data frames For working with tabular data files description:         User-friendly, type safe, runtime efficient tooling for                      working with tabular data deserialized from@@ -15,7 +15,7 @@ category:            Data build-type:          Simple extra-source-files:  benchmarks/*.hs benchmarks/*.py-                     demo/Main.hs CHANGELOG.md README.md+                     demo/framestack/app/Main.hs CHANGELOG.md README.md                      data/GetData.hs                      test/examples.toml                      test/data/managers.csv test/data/employees.csv@@ -29,7 +29,7 @@                      data/left1.csv data/right1.csv data/left_summary.csv                      data/FL2.csv cabal-version:       >=1.10-tested-with:         GHC == 8.2.2, GHC == 8.4.3, GHC == 8.6.1+tested-with:         GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.3  source-repository head   type:     git@@ -76,7 +76,7 @@                        pipes-parse >= 3.0 && < 3.1,                        pipes-safe >= 2.2.6 && < 2.4,                        bytestring,-                       vinyl >= 0.10.0 && < 0.11,+                       vinyl >= 0.10.0 && < 0.12,                        discrimination,                        contravariant,                        hashable,@@ -142,7 +142,7 @@     build-depends: base, list-t, microlens, transformers, Frames,                    vector, text, template-haskell, ghc-prim, readable,                    pipes-  hs-source-dirs: demo+  hs-source-dirs: demo/framestack/app   default-language: Haskell2010   ghc-options: -O2   -- ghc-options: -O2 -fllvm@@ -218,7 +218,7 @@   main-is:             Spec.hs   other-modules:       DataCSV PrettyTH Temp LatinTest Issue114 NoHeader                        UncurryFold UncurryFoldNoHeader UncurryFoldPartialData-                       Categorical+                       Categorical Chunks   build-depends:       base, text, hspec, Frames, template-haskell,                        temporary, directory, htoml, regex-applicative, pretty,                        unordered-containers, pipes, HUnit, vinyl,
− demo/Main.hs
@@ -1,73 +0,0 @@-{-# LANGUAGE BangPatterns, DataKinds, TemplateHaskell #-}-{-# LANGUAGE FlexibleContexts, TypeApplications, TypeOperators #-}-module Main where-import Data.Functor.Identity-import Frames-import Lens.Micro-import qualified Pipes as P-import qualified Pipes.Prelude as P--tableTypes "Row" "data/data1.csv"--tbl :: IO [Row]-tbl = runSafeT . P.toListM $ readTable "data/data1.csv"--ageDoubler :: (Age ∈ rs) => Record rs -> Record rs-ageDoubler = age %~ (* 2)--tbl2 :: IO [Row]-tbl2 = runSafeT . P.toListM $ readTable "data/data2.csv"--tbl2a :: IO [ColFun Maybe Row]-tbl2a = runSafeT . P.toListM $ readTableMaybe "data/data2.csv"--{---REPL examples:--λ> tbl >>= mapM_ print-{name :-> "joe", age :-> 21}-{name :-> "sue", age :-> 23}-{name :-> "bob", age :-> 44}-{name :-> "laura", age :-> 18}--λ> tbl2 >>= mapM_ print-{name :-> "joe", age :-> 21}-{name :-> "sue", age :-> 23}-{name :-> "laura", age :-> 18}--λ> tbl2a >>= mapM_ (putStrLn . showRecF)-{Just (name :-> "joe"), Just (age :-> 21)}-{Just (name :-> "sue"), Just (age :-> 23)}-{Just (name :-> "bob"), Nothing}-{Just (name :-> "laura"), Just (age :-> 18)}---}---- Sample data from http://support.spatialkey.com/spatialkey-sample-csv-data/--- Note: We have to replace carriage returns (\r) with line feed--- characters (\n) for the text library's line parsing to work.-tableTypes "Ins" "data/FL2.csv"--insuranceTbl :: MonadSafe m => P.Producer Ins m ()-insuranceTbl = readTable "data/FL2.csv"--insMaybe :: MonadSafe m => P.Producer (ColFun Maybe Ins) m ()-insMaybe = readTableMaybe "data/FL2.csv"--type TinyIns = Record [PolicyID, PointLatitude, PointLongitude]--main :: IO ()-main = do itbl <- inCore $ P.for insuranceTbl (P.yield . rcast)-            :: IO (P.Producer TinyIns Identity ())-          putStrLn "In-core representation prepared"-          let Identity (n,sumLat) =-                P.fold (\ !(!i,!s) r -> (i+1, s+rgetField @PointLatitude r))-                       (0::Int,0)-                       id-                       itbl-          putStrLn $ "Considering " ++ show n ++ " records..."-          putStrLn $ "Average latitude: " ++ show (sumLat / fromIntegral n)-          let Identity sumLong =-                P.fold (\ !s r -> (s + rgetField @PointLongitude r)) 0 id itbl-          putStrLn $ "Average longitude: " ++ show (sumLong / fromIntegral n)
+ demo/framestack/app/Main.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE BangPatterns, DataKinds, TemplateHaskell #-}+{-# LANGUAGE FlexibleContexts, TypeApplications, TypeOperators #-}+module Main where+import Data.Functor.Identity+import Frames+import Lens.Micro+import qualified Pipes as P+import qualified Pipes.Prelude as P++tableTypes "Row" "../../data/data1.csv"++tbl :: IO [Row]+tbl = runSafeT . P.toListM $ readTable "../../data/data1.csv"+++ageDoubler :: (Age ∈ rs) => Record rs -> Record rs+ageDoubler = age %~ (* 2)++tbl2 :: IO [Row]+tbl2 = runSafeT . P.toListM $ readTable "../../data/data2.csv"++tbl2a :: IO [ColFun Maybe Row]+tbl2a = runSafeT . P.toListM $ readTableMaybe "../../data/data2.csv"++-- Sample data from http://support.spatialkey.com/spatialkey-sample-csv-data/+-- Note: We have to replace carriage returns (\r) with line feed+-- characters (\n) for the text library's line parsing to work.+tableTypes "Ins" "../../data/FL2.csv"++insuranceTbl :: MonadSafe m => P.Producer Ins m ()+insuranceTbl = readTable "../../data/FL2.csv"++insMaybe :: MonadSafe m => P.Producer (ColFun Maybe Ins) m ()+insMaybe = readTableMaybe "../../data/FL2.csv"++type TinyIns = Record [PolicyID, PointLatitude, PointLongitude]++main :: IO ()+main = do itbl <- inCore $ P.for insuranceTbl (P.yield . rcast)+            :: IO (P.Producer TinyIns Identity ())+          putStrLn "In-core representation prepared"+          let Identity (n,sumLat) =+                P.fold (\ !(!i,!s) r -> (i+1, s+rgetField @PointLatitude r))+                       (0::Int,0)+                       id+                       itbl+          putStrLn $ "Considering " ++ show n ++ " records..."+          putStrLn $ "Average latitude: " ++ show (sumLat / fromIntegral n)+          let Identity sumLong =+                P.fold (\ !s r -> (s + rgetField @PointLongitude r)) 0 id itbl+          putStrLn $ "Average longitude: " ++ show (sumLong / fromIntegral n)
src/Frames/ColumnTypeable.hs view
@@ -6,6 +6,7 @@ import Data.Readable (Readable(fromText)) import Data.Typeable (Proxy(..), typeRep, Typeable) import qualified Data.Text as T+import Data.Int (Int32, Int64) import Data.Vinyl.Functor (Const(..)) import Language.Haskell.TH @@ -58,10 +59,21 @@ parse' :: (MonadPlus m, Parseable a) => T.Text -> m a parse' = fmap discardConfidence . parse +parseIntish :: (Readable a, MonadPlus f) => T.Text -> f (Parsed a)+parseIntish t =+  Definitely <$> fromText (fromMaybe t (T.stripSuffix (T.pack ".0") t))+ instance Parseable Bool where+ instance Parseable Int where-  parse t = Definitely <$>-            fromText (fromMaybe t (T.stripSuffix (T.pack ".0") t))+  parse = parseIntish+instance Parseable Int32 where+  parse = parseIntish+instance Parseable Int64 where+  parse = parseIntish+instance Parseable Integer where+  parse = parseIntish+ instance Parseable Float where instance Parseable Double where   -- Some CSV's export Doubles in a format like '1,000.00', filtering
src/Frames/ColumnUniverse.hs view
@@ -156,9 +156,11 @@   inferType = bestRep   {-# INLINABLE inferType #-} +#if !MIN_VERSION_vinyl(0,11,0) instance forall ts. (RPureConstrained Show ts, RecApplicative ts)   => Show (CoRec ColInfo ts) where   show x = "(Col " ++ onCoRec @Show show x ++")"+#endif    -- * Common Columns 
src/Frames/InCore.hs view
@@ -149,7 +149,8 @@ -- which provides an easier-to-use function that indexes into the -- table in a row-major fashion. inCoreSoA :: forall m rs. (PrimMonad m, RecVec rs)-          => P.Producer (Record rs) m () -> m (Int, V.Rec (((->) Int) :. ElField) rs)+          => P.Producer (Record rs) m ()+          -> m (Int, V.Rec (((->) Int) :. ElField) rs) inCoreSoA xs =   do mvs <- allocRec (Proxy :: Proxy rs) initialCapacity      let feed (!i, !sz, !mvs') row@@ -220,3 +221,39 @@ filterFrame :: RecVec rs => (Record rs -> Bool) -> FrameRec rs -> FrameRec rs filterFrame p f = runST $ inCoreAoS $ P.each f P.>-> P.filter p {-# INLINE filterFrame #-}++-- | Process a stream of 'Record's into a stream of 'Frame's that each+-- contains no more than the given number of records.+produceFrameChunks :: forall rs m. (RecVec rs, PrimMonad m)+                   => Int+                   -> P.Producer (Record rs) m ()+                   -> P.Producer (FrameRec rs) m ()+produceFrameChunks chunkSize = go+  where go src = do mutVecs <- P.lift (allocRec (Proxy :: Proxy rs) chunkSize)+                    goChunk src mutVecs 0+        goChunk src mutVecs !i+          | i >= chunkSize =+              do chunk <- P.lift (freezeFrame i mutVecs)+                 P.yield chunk+                 go src+          | otherwise =+            do maybeRow <- P.lift (P.next src)+               case maybeRow of+                 Left _ -> do+                   P.lift (freezeFrame i mutVecs) >>= P.yield+                 Right (r,src') -> do+                   P.lift (writeRec (Proxy::Proxy rs) i mutVecs r)+                   goChunk src' mutVecs (i+1)+        freezeFrame :: Int -> Record (VectorMs m rs) -> m (FrameRec rs)+        freezeFrame n =+          fmap (toAoS n . produceRec (Proxy::Proxy rs))+          . freezeRec (Proxy::Proxy rs) n+{-# INLINABLE produceFrameChunks #-}++-- | Split a 'Frame' into chunks of no more than the given number of+-- records. The underlying memory is shared with the original 'Frame'.+frameChunks :: Int -> FrameRec rs -> [FrameRec rs]+frameChunks chunkSize whole = map aux [ 0, chunkSize .. frameLength whole - 1 ]+  where aux i = Frame (min (frameLength whole - i) chunkSize)+                      (frameRow whole . (+ i))+{-# INLINABLE frameChunks #-}
+ test/Chunks.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE DataKinds, FlexibleContexts, QuasiQuotes,+             TemplateHaskell, TypeApplications #-}+module Chunks where+import Frames+import Frames.InCore (frameChunks, produceFrameChunks)+import Pipes ((>->))+import qualified Pipes.Prelude as P++tableTypes "Row" "test/data/prestige.csv"++getEducation :: Row -> Double+getEducation = rgetField @Education++chunkInCore :: IO [Double]+chunkInCore = do+  rows <- inCoreAoS (readTable "test/data/prestige.csv")+  let chunks = frameChunks 10 rows+  return $ map (getEducation . flip frameRow 0) chunks++chunkStream :: IO [Double]+chunkStream = runSafeT . P.toListM $+   produceFrameChunks 10 (readTable "test/data/prestige.csv")+   >-> P.map (getEducation . flip frameRow 0)
test/Spec.hs view
@@ -7,7 +7,7 @@ import Data.Functor.Identity import Data.Char import qualified Data.Foldable as F-import Data.List (find)+import Data.List (find, isPrefixOf) import Data.Monoid (First(..)) import qualified Data.Text as T import Language.Haskell.TH as TH@@ -15,6 +15,7 @@ import Frames import Frames.CSV (produceCSV) import Frames.CSV (defaultParser, produceTokens, defaultSep, readColHeaders)+import qualified Chunks import DataCSV import Pipes.Prelude (toListM) import PrettyTH@@ -81,7 +82,16 @@ newtype Code = Code String instance Show Code where show (Code x) = x instance Eq Code where-  Code a == Code b = filter (not . isSpace) a == filter (not . isSpace) b+  Code a == Code b = clean a == clean b+    where clean = go (removePrefix moduleNames) . filter (not . isSpace)+          go _ [] = []+          go f s@(c:cs) = case removePrefix moduleNames s of+                            Nothing -> c : go f cs+                            Just n -> go f (drop n s)+          moduleNames = [ "GHC.Maybe." ]+          removePrefix [] _ = Nothing+          removePrefix (w:ws) s | w `isPrefixOf` s = Just (length w)+                                | otherwise = removePrefix ws s  shouldBeWithinEpsilon :: Double -> Double -> Expectation shouldBeWithinEpsilon actual expected =@@ -201,3 +211,11 @@              return False)             `catch` \(_ :: ErrorCall) -> return True          it "Fails on embedded newlines" caught+       describe "Chunking" $ do+         let everyTenthEducation = [13.11,12.39,15.97,12.79,12.09,11.13,8.5,7.64,8.78,6.92,10.0]+         inCoreChunks <- H.runIO Chunks.chunkInCore+         it "Can split in-memory data into chunks" $+           inCoreChunks `shouldBe` everyTenthEducation+         streamedChunks <- H.runIO Chunks.chunkStream+         it "Can split an input stream into Frame chunks" $+           streamedChunks `shouldBe` everyTenthEducation