packages feed

Frames 0.7.3 → 0.7.4

raw patch · 14 files changed

+759/−452 lines, 14 filesdep +timedep +tomlanddep +tzdep −htomldep ~basedep ~bytestringdep ~containersnew-component:exe:timezonesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: time, tomland, tz, validation-selective

Dependencies removed: htoml

Dependency ranges changed: base, bytestring, containers, contravariant, deepseq, discrimination, ghc-prim, hashable, pipes, pipes-safe, primitive, readable, template-haskell, text, transformers, vector, vector-th-unbox

API changes (from Hackage documentation)

- Frames.ColumnTypeable: instance Frames.ColumnTypeable.Parseable GHC.Integer.Type.Integer
- Frames.ColumnUniverse: instance (Data.Text.Internal.Text Data.Vinyl.Lens.∈ ts, Data.Vinyl.Core.RPureConstrained Frames.ColumnTypeable.Parseable ts) => GHC.Base.Monoid (Data.Vinyl.CoRec.CoRec Frames.ColumnUniverse.ColInfo ts)
- Frames.ColumnUniverse: instance (Data.Vinyl.Core.RPureConstrained Frames.ColumnTypeable.Parseable ts, Data.Vinyl.CoRec.FoldRec ts ts, Data.Vinyl.Core.RecApplicative ts, Data.Text.Internal.Text Data.Vinyl.Lens.∈ ts) => Frames.ColumnTypeable.ColumnTypeable (Data.Vinyl.CoRec.CoRec Frames.ColumnUniverse.ColInfo ts)
+ Frames.ColumnTypeable: instance Frames.ColumnTypeable.Parseable GHC.Num.Integer.Integer
+ Frames.ColumnUniverse: instance (Data.Vinyl.Core.RPureConstrained Frames.ColumnTypeable.Parseable ts, Data.Vinyl.CoRec.FoldRec ts ts, Data.Vinyl.Core.RPureConstrained (Data.Vinyl.CoRec.ShowF Frames.ColumnUniverse.ColInfo) ts, Data.Vinyl.Core.RecApplicative ts, Data.Text.Internal.Text Data.Vinyl.Lens.∈ ts) => Frames.ColumnTypeable.ColumnTypeable (Data.Vinyl.CoRec.CoRec Frames.ColumnUniverse.ColInfo ts)
+ Frames.TH: colQ :: Name -> Q Exp
- Frames: (<+>) :: forall k (f :: k -> Type) (as :: [k]) (bs :: [k]). Rec f as -> Rec f bs -> Rec f (as ++ bs)
+ Frames: (<+>) :: forall {k} (f :: k -> Type) (as :: [k]) (bs :: [k]). Rec f as -> Rec f bs -> Rec f (as ++ bs)
- Frames: onCoRec :: forall k1 k2 c f (ts :: [k1]) (b :: k2) g. RPureConstrained c ts => (forall (a :: k1). (a ∈ ts, c a) => f a -> g b) -> CoRec f ts -> g b
+ Frames: onCoRec :: forall {k1} {k2} c f (ts :: [k1]) (b :: k2) g. RPureConstrained c ts => (forall (a :: k1). (a ∈ ts, c a) => f a -> g b) -> CoRec f ts -> g b
- Frames: rcast :: forall k1 k2 (rs :: [k1]) (ss :: [k1]) (f :: k2 -> Type) record (is :: [Nat]). (RecSubset record rs ss is, RecSubsetFCtx record f) => record f ss -> record f rs
+ Frames: rcast :: forall {k1} {k2} (rs :: [k1]) (ss :: [k1]) (f :: k2 -> Type) record (is :: [Nat]). (RecSubset record rs ss is, RecSubsetFCtx record f) => record f ss -> record f rs
- Frames: rsubset :: forall k1 k2 (rs :: [k2]) (ss :: [k2]) (f :: k1 -> Type) g record (is :: [Nat]). (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 :: forall {k1} k2 (rs :: [k2]) (ss :: [k2]) (f :: k1 -> Type) g record (is :: [Nat]). (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: tableTypes' :: forall a c. (c ~ CoRec ColInfo a, ColumnTypeable c, Monoid c) => RowGen a -> DecsQ
+ Frames: tableTypes' :: forall a c. (c ~ CoRec ColInfo a, ColumnTypeable c, Semigroup c, RPureConstrained (ShowF ColInfo) a) => RowGen a -> DecsQ
- Frames.CSV: prefixInference :: (ColumnTypeable a, Monoid a, Monad m) => Parser [Text] m [a]
+ Frames.CSV: prefixInference :: (ColumnTypeable a, Semigroup a, Monad m, Show a) => Parser [Text] m [a]
- Frames.CSV: readColHeaders :: (ColumnTypeable a, Monoid a, Monad m) => ParserOptions -> Producer [Text] m () -> m [(Text, a)]
+ Frames.CSV: readColHeaders :: (ColumnTypeable a, Semigroup a, Monad m, Show a) => ParserOptions -> Producer [Text] m () -> m [(Text, a)]
- Frames.RecF: rappend :: forall k (f :: k -> Type) (as :: [k]) (bs :: [k]). Rec f as -> Rec f bs -> Rec f (as ++ bs)
+ Frames.RecF: rappend :: forall {k} (f :: k -> Type) (as :: [k]) (bs :: [k]). Rec f as -> Rec f bs -> Rec f (as ++ bs)
- Frames.RecF: rtraverse :: forall u h f g (rs :: [u]). Applicative h => (forall (x :: u). () => f x -> h (g x)) -> Rec f rs -> h (Rec g rs)
+ Frames.RecF: rtraverse :: forall {u} h f g (rs :: [u]). Applicative h => (forall (x :: u). () => f x -> h (g x)) -> Rec f rs -> h (Rec g rs)
- Frames.TH: tableTypes' :: forall a c. (c ~ CoRec ColInfo a, ColumnTypeable c, Monoid c) => RowGen a -> DecsQ
+ Frames.TH: tableTypes' :: forall a c. (c ~ CoRec ColInfo a, ColumnTypeable c, Semigroup c, RPureConstrained (ShowF ColInfo) a) => RowGen a -> DecsQ
- Frames.TH: tableTypesText' :: forall a c. (c ~ CoRec ColInfo a, ColumnTypeable c, Monoid c) => RowGen a -> DecsQ
+ Frames.TH: tableTypesText' :: forall a c. (c ~ CoRec ColInfo a, ColumnTypeable c, Semigroup c) => RowGen a -> DecsQ

Files

CHANGELOG.md view
@@ -1,3 +1,6 @@+# 0.7.4+Replace the `htoml` package used in a test with `tomland`.+ # 0.7.2  - Add `writeCSVopts` that accepts options to specify the CSV delimiter.
Frames.cabal view
@@ -1,6 +1,6 @@ name:                Frames-version:             0.7.3-synopsis:            Data frames For working with tabular data files+version:             0.7.4+synopsis:            Data frames for working with tabular data files description:         User-friendly, type safe, runtime efficient tooling for                      working with tabular data deserialized from                      comma-separated values (CSV) files. The type of@@ -30,7 +30,7 @@                      data/left1.csv data/right1.csv data/left_summary.csv                      data/FL2.csv cabal-version:       >=1.10-tested-with:         GHC == 8.6.5 || == 8.8.4 || == 8.10.7 || == 9.0.1 || == 9.2.1+tested-with:         GHC == 8.6.5 || == 8.8.4 || == 8.10.7 || == 9.0.1 || == 9.2.1 || == 9.4.6  source-repository head   type:     git@@ -64,32 +64,32 @@                        TypeOperators, ConstraintKinds, StandaloneDeriving,                        UndecidableInstances, ScopedTypeVariables,                        OverloadedStrings, TypeApplications-  build-depends:       base >=4.10 && <4.17,-                       ghc-prim >=0.3 && <0.9,-                       primitive >= 0.6 && < 0.8,-                       text >= 1.1.1.0,-                       template-haskell,-                       transformers,-                       vector,-                       readable >= 0.3.1,+  build-depends:       base >=4.10 && <4.20,+                       ghc-prim >=0.3 && <0.10,+                       primitive >= 0.6 && < 0.9,+                       text >= 1.1.1.0 && < 2.1,+                       template-haskell >= 2.10 && < 2.20,+                       transformers >= 0.5.6 && < 0.6,+                       vector < 0.14,+                       readable >= 0.3.1 && < 0.4,                        pipes >= 4.1 && < 5,                        pipes-bytestring >= 2.1.6 && < 2.2,                        pipes-group >= 1.0.8 && < 1.1,                        pipes-parse >= 3.0 && < 3.1,                        pipes-safe >= 2.2.6 && < 2.4,-                       bytestring,+                       bytestring < 0.12,                        vinyl >= 0.13.0 && < 0.15,-                       discrimination,-                       contravariant,-                       hashable,-                       deepseq >= 1.4,-                       containers,-                       vector-th-unbox >= 0.2.1.6+                       discrimination >= 0.4 && < 0.6,+                       contravariant < 1.6,+                       hashable >= 1.3 && < 1.5,+                       deepseq >= 1.4 && < 1.5,+                       containers < 0.7,+                       vector-th-unbox >= 0.2.1.6 && < 0.3   hs-source-dirs:      src   default-language:    Haskell2010   ghc-options:         -Wall --- Get the large-ish data files used in the demo and benchmark+-- Get the largeish data files used in the demo and benchmark executable getdata   if !flag(demos)     buildable: False@@ -227,6 +227,17 @@   hs-source-dirs: demo   default-language: Haskell2010 +executable timezones+  if !flag(demos)+    buildable: False+  main-is: Main.hs+  other-modules: TimeIn Columns+  if flag(demos)+    build-depends: base, Frames, tz, text, time, readable,+                   pipes, pipes-safe, template-haskell+  hs-source-dirs: demo/TimeZones/src+  default-language: Haskell2010+ test-suite spec   type:                exitcode-stdio-1.0   hs-source-dirs:      test@@ -235,9 +246,9 @@                        UncurryFold UncurryFoldNoHeader UncurryFoldPartialData                        Categorical Chunks Issue145   build-depends:       base, text, hspec, Frames, template-haskell,-                       temporary, directory, htoml, regex-applicative, pretty,-                       unordered-containers, pipes, HUnit, vinyl,-                       foldl >= 1.3 && < 1.5,+                       temporary, directory, tomland, regex-applicative, pretty,+                       unordered-containers, pipes, HUnit, vinyl, +                       foldl >= 1.3 && < 1.5, validation-selective,                        attoparsec, lens, bytestring   ghc-options:         -threaded -rtsopts -with-rtsopts=-N -Wall   default-language:    Haskell2010
README.md view
@@ -130,6 +130,20 @@ There are various [demos](https://github.com/acowley/Frames/tree/master/demo) in the repository. Be sure to run the `getdata` build target to download the data files used by the demos! You can also download the data files manually and put them in a `data` directory in the directory from which you will be running the executables.  +## Contribute++You can build Frames via [nix](www.nixos.org) with the following command:+```+nix build .#Frames-8107  # or nix build .#Frames-921+```+this creates an ./result link in the current folder.++To get a development shell with all libraries, you can run:+```+nix develop .#Frames-921+```+To get just ghc and cabal in your shell, a simple `nix develop` will do.+ ## Benchmarks  The [benchmark](https://github.com/acowley/Frames/tree/master/benchmarks/InsuranceBench.hs) shows several ways of dealing with data when you want to perform multiple traversals.
benchmarks/InsuranceBench.hs view
@@ -63,7 +63,7 @@      return $! P (sumLat / fromIntegral n) (sumLong / fromIntegral n)  -- | Perform two consecutive folds after projecting a subset of an--- in-memory reprsentation.+-- in-memory representation. pipeBenchAoS :: IO (P Double) pipeBenchAoS = do tbl <- inCoreAoS' rcast tblP :: IO (Frame TinyIns)                   let (n,sumLat) =
+ demo/TimeZones/src/Columns.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeOperators #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- | Define the column types used to represent our data. Here, we wish+-- to parse data captured as 'Data.Time.LocalTime.LocalTime' values+-- into the \"America/Chicago\" time zone.+module Columns (MyColumns, TimeIn(..), Chicago(..)) where+import Data.Proxy (Proxy(..))+import Frames (CommonColumns)+import Frames.ColumnTypeable (Parseable(..))+import Frames.CSV (defaultSep, produceTokens)+import TimeIn++-- | Define a 'Parseable' instance for @TimeIn "America/Chicago"@+timeIn "America/Chicago"++-- | We need this newtype because Template Haskell can not handle the+-- type @TimeIn "America/Chicago"@ as of @GHC-8.0.1@ and+-- @template-haskell-2.11.0.0@+newtype Chicago = Chicago (TimeIn "America/Chicago") deriving Show++instance Parseable Chicago where+  parse = fmap (fmap Chicago) . parse++-- | The column types we expect our data to conform to+type MyColumns = Chicago ': CommonColumns
+ demo/TimeZones/src/Main.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+-- | Parse data including dates times in some implicit local time zone+-- into an absolute time using a supplied time zone.+module Main where+import Data.Proxy (Proxy(Proxy))+import Frames+import Frames.CSV+import Frames.TH (RowGen(columnUniverse), colQ, rowGen)+import Columns+import Pipes (Producer, (>->), runEffect)+import qualified Pipes.Prelude as P+import Pipes.Safe+import Frames (ColumnUniverse)+import Columns (MyColumns)++-- tableTypes' rowGen { columnUniverse = $(colQ ''MyColumns) } "/Users/acowley/Projects/Frames/demo/TimeZones/users.csv"+tableTypes' ((rowGen "demo/TimeZones/users.csv") { columnUniverse = Proxy @MyColumns })++loadUsers :: Producer Row (SafeT IO) ()+loadUsers = readTable "demo/TimeZones/users.csv"++main :: IO ()+main = runSafeEffect $ loadUsers >-> P.print
+ demo/TimeZones/src/TimeIn.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE RankNTypes #-}+-- | Define the 'TimeIn' type that lets us specify in the type how a+-- 'LocalTime' should be converted to a 'UTCTime'.+module TimeIn where+import Control.Monad (MonadPlus (mzero), msum)+import qualified Data.Text as T+import Data.Time.Clock+import Data.Time.Format+import Data.Time.LocalTime+import Data.Time.Zones+import Data.Time.Zones.TH+import Frames.ColumnTypeable (Parseable(..), Parsed(..))+import GHC.TypeLits+import Language.Haskell.TH++-- | A 'UTCTime' tagged with a symbol denoting the 'TZ' time zone from+-- whence it came.+newtype TimeIn (zone :: Symbol) = TimeIn UTCTime deriving Show++failZero :: MonadPlus m => Maybe r -> m r+failZero = maybe mzero pure++-- | Try to parse a 'LocalTime' value using common formats.+parseLocalTime :: MonadPlus m => T.Text -> m LocalTime+parseLocalTime t = msum (map (($ T.unpack t) . mkParser) formats)+  where formats = ["%F %T", "%F"]+        mkParser = (failZero .) . parseTimeM True defaultTimeLocale++-- | @zonedTime "America/Chicago"@ will create a 'Parseable' instance+-- for the type @TimeIn "America/Chicago"@. You can then use this type+-- when loading data.+timeIn :: String -> DecsQ+timeIn tzStr =+  do let fromLocal = [e| localTimeToUTCTZ $(includeTZFromDB tzStr) |]+         ex = [e| fmap (Definitely . TimeIn . $fromLocal) . parseLocalTime |]+     sequenceA [+       instanceD (pure [])+                 [t|Parseable (TimeIn $(pure $ LitT (StrTyLit tzStr)))|]+                 [ funD (mkName "parse") [clause [] (normalB ex) []] ] ]
src/Frames/CSV.hs view
@@ -1,19 +1,30 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveLift #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE InstanceSigs #-}-{-# LANGUAGE CPP, DataKinds, DeriveLift, FlexibleContexts, FlexibleInstances, GADTs,-             LambdaCase, OverloadedStrings, RankNTypes,-             ScopedTypeVariables, TemplateHaskell, TypeApplications,-             TypeOperators #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeOperators #-}+ -- | Infer row types from comma-separated values (CSV) data and read -- that data from files. Template Haskell is used to generate the -- necessary types so that you can write type safe programs referring -- to those types. module Frames.CSV where-import Control.Exception (try, IOException)-import Control.Monad (when, unless)++import Control.Exception (IOException, try)+import Control.Monad (unless, when) import qualified Data.ByteString.Char8 as B8 import qualified Data.Foldable as F import Data.List (intercalate)-import Data.Maybe (isNothing, fromMaybe)+import Data.Maybe (fromMaybe, isNothing) #if __GLASGOW_HASKELL__ < 808 import Data.Monoid ((<>)) #endif@@ -21,10 +32,9 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.IO as T-import Data.Vinyl (recordToList, Rec(..), ElField(..), RecordToList)-import Data.Vinyl (RecMapMethod, rmapMethod, RMap, rmap)+import Data.Vinyl (ElField (..), RMap, Rec (..), RecMapMethod, RecordToList, recordToList, rmap, rmapMethod) import Data.Vinyl.Class.Method (PayloadType)-import Data.Vinyl.Functor (Const(..), (:.), Compose(..))+import Data.Vinyl.Functor (Compose (..), Const (..), (:.)) import Frames.Col import Frames.ColumnTypeable import Frames.Rec@@ -35,11 +45,11 @@ import Language.Haskell.TH.Syntax import Pipes ((>->)) import qualified Pipes as P-import qualified Pipes.Prelude as P import qualified Pipes.Parse as P+import qualified Pipes.Prelude as P import qualified Pipes.Safe as P import qualified Pipes.Safe.Prelude as Safe-import System.IO (Handle, IOMode(ReadMode, WriteMode), hPrint, stderr)+import System.IO (Handle, IOMode (ReadMode, WriteMode), hPrint, stderr)  -- * Parsing @@ -48,31 +58,35 @@ type QuoteChar = Char  data QuotingMode-    -- | No quoting enabled. The separator may not appear in values-  = NoQuoting-    -- | Quoted values with the given quoting character. Quotes are escaped by doubling them.-    -- Mostly RFC4180 compliant, except doesn't support newlines in values-  | RFC4180Quoting QuoteChar-  deriving (Eq, Show, Lift)+    = -- | No quoting enabled. The separator may not appear in values+      NoQuoting+    | -- | Quoted values with the given quoting character. Quotes are escaped by doubling them.+      -- Mostly RFC4180 compliant, except doesn't support newlines in values+      RFC4180Quoting QuoteChar+    deriving (Eq, Show, Lift) -data ParserOptions = ParserOptions { headerOverride :: Maybe [T.Text]-                                   , columnSeparator :: Separator-                                   , quotingMode :: QuotingMode }-  deriving (Eq, Show)+data ParserOptions = ParserOptions+    { headerOverride :: Maybe [T.Text]+    , columnSeparator :: Separator+    , quotingMode :: QuotingMode+    }+    deriving (Eq, Show)  instance Lift ParserOptions where-  lift (ParserOptions Nothing sep quoting) = [|ParserOptions Nothing $sep' $quoting'|]-    where sep' = [|T.pack $(stringE $ T.unpack sep)|]-          quoting' = lift quoting-  lift (ParserOptions (Just hs) sep quoting) = [|ParserOptions (Just $hs') $sep' $quoting'|]-    where sep' = [|T.pack $(stringE $ T.unpack sep)|]-          hs' = [|map T.pack $(listE $  map (stringE . T.unpack) hs)|]-          quoting' = lift quoting+    lift (ParserOptions Nothing sep quoting) = [|ParserOptions Nothing $sep' $quoting'|]+      where+        sep' = [|T.pack $(stringE $ T.unpack sep)|]+        quoting' = lift quoting+    lift (ParserOptions (Just hs) sep quoting) = [|ParserOptions (Just $hs') $sep' $quoting'|]+      where+        sep' = [|T.pack $(stringE $ T.unpack sep)|]+        hs' = [|map T.pack $(listE $ map (stringE . T.unpack) hs)|]+        quoting' = lift quoting #if MIN_VERSION_template_haskell(2,16,0) #if MIN_VERSION_template_haskell(2,17,0)-  liftTyped = liftCode . unsafeTExpCoerce . lift+    liftTyped = liftCode . unsafeTExpCoerce . lift #else-  liftTyped = unsafeTExpCoerce . lift+    liftTyped = unsafeTExpCoerce . lift #endif #endif @@ -88,49 +102,51 @@ -- | Helper to split a 'T.Text' on commas and strip leading and -- trailing whitespace from each resulting chunk. tokenizeRow :: ParserOptions -> T.Text -> [T.Text]-tokenizeRow options =-    handleQuoting . T.splitOn sep-  where sep = columnSeparator options-        quoting = quotingMode options-        handleQuoting = case quoting of-          NoQuoting -> id-          RFC4180Quoting quote -> reassembleRFC4180QuotedParts sep quote+tokenizeRow options = handleQuoting . T.splitOn sep+  where+    sep = columnSeparator options+    quoting = quotingMode options+    handleQuoting = case quoting of+        NoQuoting -> id+        RFC4180Quoting quote -> reassembleRFC4180QuotedParts sep quote  -- | Post processing applied to a list of tokens split by the--- separator which should have quoted sections reassembeld+-- separator which should have quoted sections reassembled reassembleRFC4180QuotedParts :: Separator -> QuoteChar -> [T.Text] -> [T.Text] reassembleRFC4180QuotedParts sep quoteChar = go-  where go [] = []-        go (part:parts)-          | T.null part = T.empty : go parts-          | prefixQuoted part =+  where+    go [] = []+    go (part : parts)+        | T.null part = T.empty : go parts+        | prefixQuoted part =             if suffixQuoted part-            then unescape (T.drop 1 . T.dropEnd 1 $ part) : go parts-            else case break suffixQuoted parts of-                   (h,[]) -> [unescape (T.intercalate sep (T.drop 1 part : h))]-                   (h,t:ts) -> unescape-                                 (T.intercalate-                                    sep-                                    (T.drop 1 part : h ++ [T.dropEnd 1 t]))-                               : go ts-          | otherwise = T.strip part : go parts--        prefixQuoted t =-          T.head t == quoteChar--  &&-          -- T.length (T.takeWhile (== quoteChar) t) `rem` 2 == 1--        suffixQuoted t =-          quoteText `T.isSuffixOf` t--  &&-          -- T.length (T.takeWhileEnd (== quoteChar) t) `rem` 2 == 1+                then unescape (T.drop 1 . T.dropEnd 1 $ part) : go parts+                else case break suffixQuoted parts of+                    (h, []) -> [unescape (T.intercalate sep (T.drop 1 part : h))]+                    (h, t : ts) ->+                        unescape+                            ( T.intercalate+                                sep+                                (T.drop 1 part : h ++ [T.dropEnd 1 t])+                            )+                            : go ts+        | otherwise = T.strip part : go parts -        quoteText = T.singleton quoteChar+    prefixQuoted t =+        T.head t == quoteChar --  &&+        -- T.length (T.takeWhile (== quoteChar) t) `rem` 2 == 1+    suffixQuoted t =+        quoteText `T.isSuffixOf` t --  &&+        -- T.length (T.takeWhileEnd (== quoteChar) t) `rem` 2 == 1+    quoteText = T.singleton quoteChar -        unescape :: T.Text -> T.Text-        unescape = T.replace q2 quoteText-          where q2 = quoteText <> quoteText+    unescape :: T.Text -> T.Text+    unescape = T.replace q2 quoteText+      where+        q2 = quoteText <> quoteText ---tokenizeRow :: Separator -> T.Text -> [T.Text]---tokenizeRow sep = map (unquote . T.strip) . T.splitOn sep+-- tokenizeRow :: Separator -> T.Text -> [T.Text]+-- tokenizeRow sep = map (unquote . T.strip) . T.splitOn sep --  where unquote txt --          | quoted txt = case T.dropEnd 1 (T.drop 1 txt) of --                           txt' | T.null txt' -> "Col"@@ -145,143 +161,180 @@  -- | Infer column types from a prefix (up to 1000 lines) of a CSV -- file.-prefixInference :: (ColumnTypeable a, Monoid a, Monad m)-                => P.Parser [T.Text] m [a]-prefixInference = P.draw >>= \case-  Nothing -> return []-  Just row1 -> P.foldAll (\ts -> zipWith (<>) ts . inferCols)-                         (inferCols row1)-                         id-  where inferCols = map inferType+prefixInference ::+    (ColumnTypeable a, Semigroup a, Monad m, Show a) =>+    P.Parser [T.Text] m [a]+prefixInference =+    P.draw >>= \case+        Nothing -> return []+        Just row1 ->+            P.foldAll+                (\ts -> zipWith (<>) ts . inferCols)+                (inferCols row1)+                id+  where+    inferCols = map inferType  -- | Extract column names and inferred types from a CSV file.-readColHeaders :: (ColumnTypeable a, Monoid a, Monad m)-               => ParserOptions -> P.Producer [T.Text] m () -> m [(T.Text, a)]+readColHeaders ::+    (ColumnTypeable a, Semigroup a, Monad m, Show a) =>+    ParserOptions+    -> P.Producer [T.Text] m ()+    -> m [(T.Text, a)] readColHeaders opts = P.evalStateT $-  do headerRow <- maybe (fromMaybe err <$> P.draw)-                        pure-                        (headerOverride opts)-     colTypes <- prefixInference-     unless (length headerRow == length colTypes) (error errNumColumns)-     return (zip headerRow colTypes)-  where err = error "Empty Producer has no header row"-        errNumColumns =-          unlines-          [ ""-          , "Error parsing CSV: "-          , "  Number of columns in header differs from number of columns"-          , "  found in the remaining file. This may be due to newlines"-          , "  being present within the data itself (not just separating"-          , "  rows). If support for embedded newlines is required, "-          , "  consider using the Frames-dsv package in conjunction with"-          , "  Frames to make use of a different CSV parser."]+    do+        headerRow <-+            maybe+                (fromMaybe err <$> P.draw)+                pure+                (headerOverride opts)+        colTypes <- prefixInference+        unless (length headerRow == length colTypes) (error errNumColumns)+        return (zip headerRow colTypes)+  where+    err = error "Empty Producer has no header row"+    errNumColumns =+        unlines+            [ ""+            , "Error parsing CSV: "+            , "  Number of columns in header differs from number of columns"+            , "  found in the remaining file. This may be due to newlines"+            , "  being present within the data itself (not just separating"+            , "  rows). If support for embedded newlines is required, "+            , "  consider using the Frames-dsv package in conjunction with"+            , "  Frames to make use of a different CSV parser."+            ]  -- * Loading CSV Data  -- | Parsing each component of a 'RecF' from a list of text chunks, -- one chunk per record component. class ReadRec rs where-  readRec :: [T.Text] -> Rec (Either T.Text :. ElField) rs+    readRec :: [T.Text] -> Rec (Either T.Text :. ElField) rs  instance ReadRec '[] where-  readRec _ = RNil+    readRec _ = RNil  instance (Parseable t, ReadRec ts, KnownSymbol s) => ReadRec (s :-> t ': ts) where-  readRec [] = Compose (Left mempty) :& readRec []-  readRec (h:t) = maybe (Compose (Left (T.copy h)))-                        (Compose . Right . Field)-                        (parse' h) :& readRec t+    readRec [] = Compose (Left mempty) :& readRec []+    readRec (h : t) =+        maybe+            (Compose (Left (T.copy h)))+            (Compose . Right . Field)+            (parse' h)+            :& readRec t  -- | Opens a file (in 'P.MonadSafe') and repeatedly applies the given -- function to the 'Handle' to obtain lines to yield. Adapted from the -- moribund pipes-text package.-pipeLines :: P.MonadSafe m-          => (Handle -> IO (Either IOException T.Text))-          -> FilePath-          -> P.Producer T.Text m ()+pipeLines ::+    (P.MonadSafe m) =>+    (Handle -> IO (Either IOException T.Text))+    -> FilePath+    -> P.Producer T.Text m () pipeLines pgetLine fp = Safe.withFile fp ReadMode $ \h ->-  let loop = do txt <- P.liftIO (pgetLine h)-                case txt of-                  Left _e -> return ()-                  Right y -> P.yield y >> loop-  in loop+    let loop = do+            txt <- P.liftIO (pgetLine h)+            case txt of+                Left _e -> return ()+                Right y -> P.yield y >> loop+     in loop  -- | Produce lines of 'T.Text'.-produceTextLines :: P.MonadSafe m => FilePath -> P.Producer T.Text m ()+produceTextLines :: (P.MonadSafe m) => FilePath -> P.Producer T.Text m () produceTextLines = pipeLines (try . T.hGetLine)  -- | Produce lines of tokens that were separated by the given -- separator.-produceTokens :: P.MonadSafe m-              => FilePath-              -> Separator-              -> P.Producer [T.Text] m ()+produceTokens ::+    (P.MonadSafe m) =>+    FilePath+    -> Separator+    -> P.Producer [T.Text] m () produceTokens fp sep = produceTextLines fp >-> P.map tokenize-  where tokenize = tokenizeRow popts-        popts = defaultParser { columnSeparator = sep }+  where+    tokenize = tokenizeRow popts+    popts = defaultParser{columnSeparator = sep}  -- | Consume lines of 'T.Text', writing them to a file.-consumeTextLines :: P.MonadSafe m => FilePath -> P.Consumer T.Text m r+consumeTextLines :: (P.MonadSafe m) => FilePath -> P.Consumer T.Text m r consumeTextLines fp = Safe.withFile fp WriteMode $ \h ->-  let loop = P.await >>= P.liftIO . T.hPutStrLn h >> loop-  in loop+    let loop = P.await >>= P.liftIO . T.hPutStrLn h >> loop+     in loop  -- | Produce the lines of a latin1 (or ISO8859 Part 1) encoded file as -- ’T.Text’ values.-readFileLatin1Ln :: P.MonadSafe m => FilePath -> P.Producer [T.Text] m ()-readFileLatin1Ln fp = pipeLines (try . fmap T.decodeLatin1 . B8.hGetLine) fp-                      >-> P.map (tokenizeRow defaultParser)+readFileLatin1Ln :: (P.MonadSafe m) => FilePath -> P.Producer [T.Text] m ()+readFileLatin1Ln fp =+    pipeLines (try . fmap T.decodeLatin1 . B8.hGetLine) fp+        >-> P.map (tokenizeRow defaultParser)  -- | Read a 'RecF' from one line of CSV.-readRow :: ReadRec rs-        => ParserOptions -> T.Text -> Rec (Either T.Text :. ElField) rs+readRow ::+    (ReadRec rs) =>+    ParserOptions+    -> T.Text+    -> Rec (Either T.Text :. ElField) rs readRow = (readRec .) . tokenizeRow  -- | Produce rows where any given entry can fail to parse.-readTableMaybeOpt :: (P.MonadSafe m, ReadRec rs, RMap rs)-                  => ParserOptions-                  -> FilePath-                  -> P.Producer (Rec (Maybe :. ElField) rs) m ()+readTableMaybeOpt ::+    (P.MonadSafe m, ReadRec rs, RMap rs) =>+    ParserOptions+    -> FilePath+    -> P.Producer (Rec (Maybe :. ElField) rs) m () readTableMaybeOpt opts csvFile =-  produceTokens csvFile (columnSeparator opts) >-> pipeTableMaybeOpt opts+    produceTokens csvFile (columnSeparator opts) >-> pipeTableMaybeOpt opts  -- | Stream lines of CSV data into rows of ’Rec’ values values where -- any given entry can fail to parse.-pipeTableMaybeOpt :: (Monad m, ReadRec rs, RMap rs)-                  => ParserOptions-                  -> P.Pipe [T.Text] (Rec (Maybe :. ElField) rs) m ()+pipeTableMaybeOpt ::+    (Monad m, ReadRec rs, RMap rs) =>+    ParserOptions+    -> P.Pipe [T.Text] (Rec (Maybe :. ElField) rs) m () pipeTableMaybeOpt opts = do-  when (isNothing (headerOverride opts)) (() <$ P.await)-  P.map (rmap (either (const (Compose Nothing))-                      (Compose . Just) . getCompose)-         . readRec)+    when (isNothing (headerOverride opts)) (() <$ P.await)+    P.map+        ( rmap+            ( either+                (const (Compose Nothing))+                (Compose . Just)+                . getCompose+            )+            . readRec+        )  -- | Stream lines of CSV data into rows of ’Rec’ values values where -- any given entry can fail to parse. In the case of a parse failure, the -- raw 'T.Text' of that entry is retained.-pipeTableEitherOpt :: (Monad m, ReadRec rs)-                   => ParserOptions-                   -> P.Pipe T.Text (Rec (Either T.Text :. ElField) rs) m ()+pipeTableEitherOpt ::+    (Monad m, ReadRec rs) =>+    ParserOptions+    -> P.Pipe T.Text (Rec (Either T.Text :. ElField) rs) m () pipeTableEitherOpt opts = do-  when (isNothing (headerOverride opts)) (() <$ P.await)-  P.map (readRow opts)+    when (isNothing (headerOverride opts)) (() <$ P.await)+    P.map (readRow opts)  -- | Produce rows where any given entry can fail to parse.-readTableMaybe :: (P.MonadSafe m, ReadRec rs, RMap rs)-               => FilePath -> P.Producer (Rec (Maybe :. ElField) rs) m ()+readTableMaybe ::+    (P.MonadSafe m, ReadRec rs, RMap rs) =>+    FilePath+    -> P.Producer (Rec (Maybe :. ElField) rs) m () readTableMaybe = readTableMaybeOpt defaultParser  -- | Stream lines of CSV data into rows of ’Rec’ values where any -- given entry can fail to parse.-pipeTableMaybe :: (Monad m, ReadRec rs, RMap rs)-               => P.Pipe [T.Text] (Rec (Maybe :. ElField) rs) m ()+pipeTableMaybe ::+    (Monad m, ReadRec rs, RMap rs) =>+    P.Pipe [T.Text] (Rec (Maybe :. ElField) rs) m () pipeTableMaybe = pipeTableMaybeOpt defaultParser  -- | Stream lines of CSV data into rows of ’Rec’ values where any -- given entry can fail to parse. In the case of a parse failure, the -- raw 'T.Text' of that entry is retained.-pipeTableEither :: (Monad m, ReadRec rs)-                => P.Pipe T.Text (Rec (Either T.Text :. ElField) rs) m ()+pipeTableEither ::+    (Monad m, ReadRec rs) =>+    P.Pipe T.Text (Rec (Either T.Text :. ElField) rs) m () pipeTableEither = pipeTableEitherOpt defaultParser  -- -- | Returns a `MonadPlus` producer of rows for which each column was@@ -308,113 +361,170 @@  -- | Returns a producer of rows for which each column was successfully -- parsed.-readTableOpt :: (P.MonadSafe m, ReadRec rs, RMap rs)-             => ParserOptions -> FilePath -> P.Producer (Record rs) m ()+readTableOpt ::+    (P.MonadSafe m, ReadRec rs, RMap rs) =>+    ParserOptions+    -> FilePath+    -> P.Producer (Record rs) m () readTableOpt opts csvFile = readTableMaybeOpt opts csvFile P.>-> go-  where go = P.await >>= maybe go (\x -> P.yield x >> go) . recMaybe+  where+    go = P.await >>= maybe go (\x -> P.yield x >> go) . recMaybe  -- | Pipe lines of CSV text into rows for which each column was -- successfully parsed.-pipeTableOpt :: (ReadRec rs, RMap rs, Monad m)-             => ParserOptions -> P.Pipe [T.Text] (Record rs) m ()+pipeTableOpt ::+    (ReadRec rs, RMap rs, Monad m) =>+    ParserOptions+    -> P.Pipe [T.Text] (Record rs) m () pipeTableOpt opts = pipeTableMaybeOpt opts >-> P.map recMaybe >-> P.concat  -- | Returns a producer of rows for which each column was successfully -- parsed.-readTable :: (P.MonadSafe m, ReadRec rs, RMap rs)-          => FilePath -> P.Producer (Record rs) m ()+readTable ::+    (P.MonadSafe m, ReadRec rs, RMap rs) =>+    FilePath+    -> P.Producer (Record rs) m () readTable = readTableOpt defaultParser -readRecEither :: (ReadRec rs, RMap rs)-              => [T.Text] -> Either (Rec (Either T.Text :. ElField) rs) (Record rs)-readRecEither tokens = let tmp = readRec tokens-                       in case rtraverse getCompose tmp of-                            Right r -> Right r-                            _ -> Left tmp+readRecEither ::+    (ReadRec rs, RMap rs) =>+    [T.Text]+    -> Either (Rec (Either T.Text :. ElField) rs) (Record rs)+readRecEither tokens =+    let tmp = readRec tokens+     in case rtraverse getCompose tmp of+            Right r -> Right r+            _ -> Left tmp  -- | Similar to 'readTable' except that rows that fail to parse are -- printed to @stderr@ with columns that failed to parse printed as -- @"Left rawtext"@ while those that were successfully parsed are -- shown as @"Right text"@.-readTableDebug :: forall m rs.-                  (P.MonadSafe m, ReadRec rs, RMap rs,-                   RecMapMethod ShowCSV (Either T.Text :. ElField) rs,-                   RecordToList rs)-               => FilePath -> P.Producer (Record rs) m ()+readTableDebug ::+    forall m rs.+    ( P.MonadSafe m+    , ReadRec rs+    , RMap rs+    , RecMapMethod ShowCSV (Either T.Text :. ElField) rs+    , RecordToList rs+    ) =>+    FilePath+    -> P.Producer (Record rs) m () readTableDebug csvFile =-  produceTokens csvFile (columnSeparator opts) >-> go >-> debugAll-  where opts = defaultParser-        go = do-          when (isNothing (headerOverride opts)) (() <$ P.await)-          P.map readRecEither-        debugAll = do-          P.await >>= either (P.liftIO . hPrint stderr . debugOne) P.yield-          debugAll-        debugOne = recordToList . rmapMethod @ShowCSV (aux . getCompose)-        aux :: (ShowCSV (PayloadType ElField a))-            => Either T.Text (ElField a) -> Const T.Text a-        aux (Right (Field x)) = Const ("Right " <> showCSV x)-        aux (Left txt) = Const ("Left " <> txt)+    produceTokens csvFile (columnSeparator opts) >-> go >-> debugAll+  where+    opts = defaultParser+    go = do+        when (isNothing (headerOverride opts)) (() <$ P.await)+        P.map readRecEither+    debugAll = do+        P.await >>= either (P.liftIO . hPrint stderr . debugOne) P.yield+        debugAll+    debugOne = recordToList . rmapMethod @ShowCSV (aux . getCompose)+    aux ::+        (ShowCSV (PayloadType ElField a)) =>+        Either T.Text (ElField a)+        -> Const T.Text a+    aux (Right (Field x)) = Const ("Right " <> showCSV x)+    aux (Left txt) = Const ("Left " <> txt)  -- | Pipe lines of CSV text into rows for which each column was -- successfully parsed.-pipeTable :: (ReadRec rs, RMap rs, Monad m)-          => P.Pipe [T.Text] (Record rs) m ()+pipeTable ::+    (ReadRec rs, RMap rs, Monad m) =>+    P.Pipe [T.Text] (Record rs) m () pipeTable = pipeTableOpt defaultParser  -- * Writing CSV Data -showFieldsCSV :: (RecMapMethod ShowCSV ElField ts, RecordToList ts)-              => Record ts -> [T.Text]+showFieldsCSV ::+    (RecMapMethod ShowCSV ElField ts, RecordToList ts) =>+    Record ts+    -> [T.Text] showFieldsCSV = recordToList . rmapMethod @ShowCSV aux-  where aux :: (ShowCSV (PayloadType ElField a))-            => ElField a -> Const T.Text a-        aux (Field x) = Const (showCSV x)+  where+    aux ::+        (ShowCSV (PayloadType ElField a)) =>+        ElField a+        -> Const T.Text a+    aux (Field x) = Const (showCSV x)  -- | 'P.yield' a header row with column names followed by a line of -- text for each 'Record' with each field separated by a comma. If -- your source of 'Record' values is a 'P.Producer', consider using -- 'pipeToCSV' to keep everything streaming.-produceCSV :: forall f ts m.-              (ColumnHeaders ts, Foldable f, Monad m, RecordToList ts,-              RecMapMethod ShowCSV ElField ts)-           => f (Record ts) -> P.Producer String m ()+produceCSV ::+    forall f ts m.+    ( ColumnHeaders ts+    , Foldable f+    , Monad m+    , RecordToList ts+    , RecMapMethod ShowCSV ElField ts+    ) =>+    f (Record ts)+    -> P.Producer String m () produceCSV = produceDSV defaultParser -produceDSV :: forall f ts m.-              (ColumnHeaders ts, Foldable f, Monad m, RecordToList ts,-              RecMapMethod ShowCSV ElField ts)-           => ParserOptions -> f (Record ts) -> P.Producer String m ()+produceDSV ::+    forall f ts m.+    ( ColumnHeaders ts+    , Foldable f+    , Monad m+    , RecordToList ts+    , RecMapMethod ShowCSV ElField ts+    ) =>+    ParserOptions+    -> f (Record ts)+    -> P.Producer String m () produceDSV opts recs = do-  P.yield (intercalate (T.unpack separator) (columnHeaders (Proxy :: Proxy (Record ts))))-  F.mapM_ (P.yield . T.unpack . T.intercalate separator . showFieldsCSV) recs+    P.yield (intercalate (T.unpack separator) (columnHeaders (Proxy :: Proxy (Record ts))))+    F.mapM_ (P.yield . T.unpack . T.intercalate separator . showFieldsCSV) recs   where     separator = columnSeparator opts  -- | 'P.yield' a header row with column names followed by a line of -- text for each 'Record' with each field separated by a comma. This--- is the same as 'produceCSV', but adapated for cases where you have+-- is the same as 'produceCSV', but adapted for cases where you have -- streaming input that you wish to use to produce streaming output.-pipeToCSV :: forall ts m.-             (Monad m, ColumnHeaders ts, RecordToList ts,-              RecMapMethod ShowCSV ElField ts)-          => P.Pipe (Record ts) T.Text m ()+pipeToCSV ::+    forall ts m.+    ( Monad m+    , ColumnHeaders ts+    , RecordToList ts+    , RecMapMethod ShowCSV ElField ts+    ) =>+    P.Pipe (Record ts) T.Text m () pipeToCSV = P.yield (T.intercalate "," (map T.pack header)) >> go-  where header = columnHeaders (Proxy :: Proxy (Record ts))-        go :: P.Pipe (Record ts) T.Text m ()-        go = P.map (T.intercalate "," . showFieldsCSV)+  where+    header = columnHeaders (Proxy :: Proxy (Record ts))+    go :: P.Pipe (Record ts) T.Text m ()+    go = P.map (T.intercalate "," . showFieldsCSV)  -- | Write a header row with column names followed by a line of text -- for each 'Record' to the given file.-writeCSV :: (ColumnHeaders ts, Foldable f, RecordToList ts,-             RecMapMethod ShowCSV ElField ts)-         => FilePath -> f (Record ts) -> IO ()-writeCSV  = writeDSV defaultParser+writeCSV ::+    ( ColumnHeaders ts+    , Foldable f+    , RecordToList ts+    , RecMapMethod ShowCSV ElField ts+    ) =>+    FilePath+    -> f (Record ts)+    -> IO ()+writeCSV = writeDSV defaultParser  -- | Write a header row with column names followed by a line of text -- for each 'Record' to the given file.-writeDSV :: (ColumnHeaders ts, Foldable f, RecordToList ts,-             RecMapMethod ShowCSV ElField ts)-         => ParserOptions -> FilePath -> f (Record ts) -> IO ()-writeDSV opts fp recs = P.runSafeT . P.runEffect $-                   produceDSV opts recs >-> P.map T.pack >-> consumeTextLines fp+writeDSV ::+    ( ColumnHeaders ts+    , Foldable f+    , RecordToList ts+    , RecMapMethod ShowCSV ElField ts+    ) =>+    ParserOptions+    -> FilePath+    -> f (Record ts)+    -> IO ()+writeDSV opts fp recs =+    P.runSafeT . P.runEffect $+        produceDSV opts recs >-> P.map T.pack >-> consumeTextLines fp
src/Frames/ColumnTypeable.hs view
@@ -1,5 +1,4 @@-{-# LANGUAGE BangPatterns, DefaultSignatures, LambdaCase,-             ScopedTypeVariables #-}+{-# LANGUAGE DefaultSignatures, ScopedTypeVariables #-} module Frames.ColumnTypeable where import Control.Monad (MonadPlus) import Data.Maybe (fromMaybe)
src/Frames/ColumnUniverse.hs view
@@ -1,161 +1,226 @@-{-# LANGUAGE BangPatterns, CPP, ConstraintKinds, DataKinds,-             FlexibleContexts, FlexibleInstances, GADTs, InstanceSigs,-             KindSignatures, LambdaCase, MultiParamTypeClasses,-             OverloadedStrings, QuasiQuotes, RankNTypes,-             ScopedTypeVariables, TemplateHaskell, TypeApplications,-             TypeFamilies, TypeOperators, UndecidableInstances #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-}+ module Frames.ColumnUniverse (-  CoRec, Columns, ColumnUniverse, ColInfo,-  CommonColumns, CommonColumnsCat, parsedTypeRep+    CoRec,+    Columns,+    ColumnUniverse,+    ColInfo,+    CommonColumns,+    CommonColumnsCat,+    parsedTypeRep, ) where+ import Data.Maybe (fromMaybe) #if __GLASGOW_HASKELL__ < 808 import Data.Semigroup (Semigroup((<>))) #endif+import Data.Either (fromRight) import qualified Data.Text as T import Data.Vinyl import Data.Vinyl.CoRec import Data.Vinyl.Functor-import Data.Vinyl.TypeLevel (RIndex, NatToInt)-import Frames.ColumnTypeable+import Data.Vinyl.TypeLevel (NatToInt, RIndex) import Frames.Categorical+import Frames.ColumnTypeable import Language.Haskell.TH  -- | Extract a function to test whether some value of a given type -- could be read from some 'T.Text'.-inferParseable :: Parseable a => T.Text -> (Maybe :. Parsed) a+inferParseable :: (Parseable a) => T.Text -> (Maybe :. Parsed) a inferParseable = Compose . parse  -- | Helper to call 'inferParseable' on variants of a 'CoRec'.-inferParseable' :: Parseable a => (((->) T.Text) :. (Maybe :. Parsed)) a+inferParseable' :: (Parseable a) => ((->) T.Text :. (Maybe :. Parsed)) a inferParseable' = Compose inferParseable  -- * Record Helpers -tryParseAll :: forall ts. (RecApplicative ts, RPureConstrained Parseable ts)-            => T.Text -> Rec (Maybe :. Parsed) ts+tryParseAll ::+    forall ts.+    (RecApplicative ts, RPureConstrained Parseable ts) =>+    T.Text+    -> Rec (Maybe :. Parsed) ts tryParseAll = rtraverse getCompose funs-  where funs :: Rec (((->) T.Text) :. (Maybe :. Parsed)) ts-        funs = rpureConstrained @Parseable inferParseable'+  where+    funs :: Rec (((->) T.Text) :. (Maybe :. Parsed)) ts+    funs = rpureConstrained @Parseable inferParseable'  -- * Column Type Inference  -- | Information necessary for synthesizing row types and comparing -- types. newtype ColInfo a = ColInfo (Either (String -> Q [Dec]) Type, Parsed a)-instance Show a => Show (ColInfo a) where-  show (ColInfo (t,p)) = "(ColInfo {"-                         ++ either (const "cat") show t-                         ++ ", "-                         ++ show (discardConfidence p) ++"})" -parsedToColInfo :: Parseable a => Parsed a -> ColInfo a+instance (Show a) => Show (ColInfo a) where+    show (ColInfo (t, p)) =+        "(ColInfo {"+            ++ either (const "cat") show t+            ++ ", "+            ++ show (discardConfidence p)+            ++ "})"++parsedToColInfo :: (Parseable a) => Parsed a -> ColInfo a parsedToColInfo x = case getConst rep of-                      Left dec -> ColInfo (Left dec, x)-                      Right ty ->-                        ColInfo (Right ty, x)-  where rep = representableAsType x+    Left dec -> ColInfo (Left dec, x)+    Right ty ->+        ColInfo (Right ty, x)+  where+    rep = representableAsType x  parsedTypeRep :: ColInfo a -> Parsed Type-parsedTypeRep (ColInfo (t,p)) =-  const (either (const (ConT (mkName "Categorical"))) id t) <$> p+parsedTypeRep (ColInfo (t, p)) =+    fromRight (ConT (mkName "Categorical")) t <$ p  -- | Map 'Type's we know about (with a special treatment of -- synthesized types for categorical variables) to 'Int's for ordering -- purposes. orderParsePriorities :: Parsed (Maybe Type) -> Maybe Int orderParsePriorities x =-  case discardConfidence x of-    Nothing -> Just 1 -- categorical variable-    Just t-      | t == tyText -> Just (0 + uncertainty)-      | t == tyDbl -> Just (2 + uncertainty)-      | t == tyInt -> Just (3 + uncertainty)-      | t == tyBool -> Just (4 + uncertainty)-      | otherwise -> Nothing-  where tyText = ConT (mkName "Text")-        tyDbl = ConT (mkName "Double")-        tyInt = ConT (mkName "Int")-        tyBool = ConT (mkName "Bool")-        uncertainty = case x of Definitely _ -> 0; Possibly _ -> 5+    case discardConfidence x of+        Nothing -> Just (1 + 6) -- categorical variable+        Just t+            | t == tyText -> Just (0 + uncertainty)+            | t == tyDbl -> Just (2 + uncertainty)+            | t == tyInt -> Just (3 + uncertainty)+            | t == tyBool -> Just (4 + uncertainty)+            | otherwise -> Just (5 + uncertainty) -- Unknown type+  where+    tyText = ConT (mkName "Text")+    tyDbl = ConT (mkName "Double")+    tyInt = ConT (mkName "Int")+    tyBool = ConT (mkName "Bool")+    uncertainty = case x of Definitely _ -> 0; Possibly _ -> 6  -- | We use a join semi-lattice on types for representations. The--- bottom of the lattice is effectively an error (we have nothing to--- represent), @Bool < Int@, @Int < Double@, and @forall n. n <= Text@.+--  bottom of the lattice is effectively an error (we have nothing to+--  represent), @Bool < Int@, @Int < Double@, and @forall n. n <= Text@. ----- The high-level goal here is that we will pick the "greater" of two--- choices in 'bestRep'. A 'Definitely' parse result is preferred over--- a 'Possibly' parse result. If we have two distinct 'Possibly' parse--- results, we give up. If we have two distinct 'Definitely' parse--- results, we are in dangerous waters: all data is parseable at--- /both/ types, so which do we default to? The defaulting choices--- made here are described in the previous paragraph. If there is no--- defaulting rule, we give up (i.e. use 'T.Text' as a--- representation).+--  The high-level goal here is that we will pick the "greater" of two+--  choices in 'bestRep'. A 'Definitely' parse result is preferred over+--  a 'Possibly' parse result. If we have two distinct 'Possibly' parse+--  results, we give up. If we have two distinct 'Definitely' parse+--  results, we are in dangerous waters: all data is parseable at+--  /both/ types, so which do we default to? The defaulting choices+--  made here are described in the previous paragraph. If there is no+--  defaulting rule, we give up (i.e. use 'T.Text' as a+--  representation). lubTypes :: Parsed (Maybe Type) -> Parsed (Maybe Type) -> Maybe Ordering lubTypes x y = compare <$> orderParsePriorities y <*> orderParsePriorities x -instance (T.Text ∈ ts, RPureConstrained Parseable ts) => Monoid (CoRec ColInfo ts) where-  mempty = CoRec (ColInfo ( Right (ConT (mkName "Text")), Possibly T.empty))+-- instance (T.Text ∈ ts, RPureConstrained Parseable ts) => Monoid (CoRec ColInfo ts) where+--     mempty = CoRec (ColInfo (Right (ConT (mkName "Text")), Possibly T.empty))  -- | A helper For the 'Semigroup' instance below.-mergeEqTypeParses :: forall ts. (RPureConstrained Parseable ts, T.Text ∈ ts)-                  => CoRec ColInfo ts -> CoRec ColInfo ts -> CoRec ColInfo ts-mergeEqTypeParses x@(CoRec _) y = fromMaybe definitelyText-                                $ coRecTraverse getCompose-                                                (coRecMapC @Parseable aux x)-  where definitelyText = CoRec (ColInfo (Right (ConT (mkName "Text")), Definitely T.empty))-        aux :: forall a. (Parseable a, NatToInt (RIndex a ts))-            => ColInfo a -> (Maybe :. ColInfo) a-        aux (ColInfo (_, pX)) =-          case asA' @a y of+mergeEqTypeParses ::+    forall ts.+    (RPureConstrained Parseable ts, T.Text ∈ ts) =>+    CoRec ColInfo ts+    -> CoRec ColInfo ts+    -> CoRec ColInfo ts+mergeEqTypeParses x@(CoRec _) y =+    fromMaybe definitelyText $+        coRecTraverse+            getCompose+            (coRecMapC @Parseable aux x)+  where+    definitelyText = CoRec (ColInfo (Right (ConT (mkName "Text")), Definitely T.empty))+    aux ::+        forall a.+        (Parseable a, NatToInt (RIndex a ts)) =>+        ColInfo a+        -> (Maybe :. ColInfo) a+    aux (ColInfo (_, pX)) =+        case asA' @a y of             Nothing -> Compose Nothing             Just (ColInfo (_, pY)) ->-              maybe (Compose Nothing)+                maybe+                    (Compose Nothing)                     (Compose . Just . parsedToColInfo)                     (parseCombine pX pY) -instance (T.Text ∈ ts, RPureConstrained Parseable ts)-  => Semigroup (CoRec ColInfo ts) where-  x@(CoRec (ColInfo (tyX, pX))) <> y@(CoRec (ColInfo (tyY, pY))) =-    case lubTypes (const (either (const Nothing) Just tyX) <$> pX)-                  (const (either (const Nothing) Just tyY) <$> pY) of-      Just GT -> x-      Just LT -> y-      Just EQ -> mergeEqTypeParses x y-      Nothing -> mempty+instance+    (T.Text ∈ ts, RPureConstrained Parseable ts) =>+    Semigroup (CoRec ColInfo ts)+    where+    (<>) :: (T.Text ∈ ts, RPureConstrained Parseable ts) => CoRec ColInfo ts -> CoRec ColInfo ts -> CoRec ColInfo ts+    x@(CoRec (ColInfo (tyX, pX))) <> y@(CoRec (ColInfo (tyY, pY))) =+        case lubTypes+            (either (const Nothing) Just tyX <$ pX)+            (either (const Nothing) Just tyY <$ pY) of+            Just GT -> x+            Just LT -> y+            Just EQ -> mergeEqTypeParses x y+            Nothing -> undefined -- mempty  -- | Find the best (i.e. smallest) 'CoRec' variant to represent a--- parsed value. For inspection in GHCi after loading this module,--- consider this example:+--  parsed value. For inspection in GHCi after loading this module,+--  consider this example: ----- >>> :set -XTypeApplications--- >>> :set -XOverloadedStrings--- >>> import Data.Vinyl.CoRec (foldCoRec)--- >>> foldCoRec parsedTypeRep (bestRep @CommonColumns "2.3")--- Definitely Double-bestRep :: forall ts.-           (RPureConstrained Parseable ts,-            FoldRec ts ts,-            RecApplicative ts, T.Text ∈ ts)-        => T.Text -> CoRec ColInfo ts+--  >>> :set -XTypeApplications+--  >>> :set -XOverloadedStrings+--  >>> import Data.Vinyl.CoRec (foldCoRec)+--  >>> foldCoRec parsedTypeRep (bestRep @CommonColumns "2.3")+--  Definitely Double+bestRep ::+    forall ts.+    ( RPureConstrained Parseable ts+    , RPureConstrained (ShowF ColInfo) ts+    , FoldRec ts ts+    , RecApplicative ts+    , T.Text ∈ ts+    ) =>+    T.Text+    -> CoRec ColInfo ts bestRep t-  | T.null t || t == "NA" = (CoRec (parsedToColInfo (Possibly T.empty)))-  | otherwise = coRecMapC @Parseable parsedToColInfo-              . fromMaybe (CoRec (Possibly T.empty :: Parsed T.Text))-              . firstField-              . (tryParseAll :: T.Text -> Rec (Maybe :. Parsed) ts)-              $ t-{-# INLINABLE bestRep #-}+    -- \| trace (show (aux t)) False = undefined+    | T.null t || t == "NA" = CoRec (parsedToColInfo (Possibly T.empty))+    | otherwise =+        coRecMapC @Parseable parsedToColInfo+            . fromMaybe (CoRec (Possibly T.empty :: Parsed T.Text))+            . firstField+            . (tryParseAll :: T.Text -> Rec (Maybe :. Parsed) ts)+            $ t+-- where+--   aux =+--       coRecMapC @Parseable parsedToColInfo+--           . fromMaybe (CoRec (Possibly T.empty :: Parsed T.Text))+--           . firstField+--           . (tryParseAll :: T.Text -> Rec (Maybe :. Parsed) ts)+{-# INLINEABLE bestRep #-} -instance (RPureConstrained Parseable ts, FoldRec ts ts,-          RecApplicative ts, T.Text ∈ ts) =>-    ColumnTypeable (CoRec ColInfo ts) where-  colType (CoRec (ColInfo (t, _))) = t-  {-# INLINE colType #-}-  inferType = bestRep-  {-# INLINABLE inferType #-}+instance+    ( RPureConstrained Parseable ts+    , FoldRec ts ts+    , RPureConstrained (ShowF ColInfo) ts+    , RecApplicative ts+    , T.Text ∈ ts+    ) =>+    ColumnTypeable (CoRec ColInfo ts)+    where+    colType (CoRec (ColInfo (t, _))) = t+    {-# INLINE colType #-}+    inferType = bestRep+    {-# INLINEABLE inferType #-}  #if !MIN_VERSION_vinyl(0,11,0) instance forall ts. (RPureConstrained Show ts, RecApplicative ts)@@ -175,7 +240,7 @@ type ColumnUniverse = CoRec ColInfo  -- | A universe of common column variants. These are the default--- column types that @Frames@ can infer. See the--- <http://acowley.github.io/Frames/#sec-4 Tutorial> for an example of--- extending the default types with your own.+--  column types that @Frames@ can infer. See the+--  <http://acowley.github.io/Frames/#sec-4 Tutorial> for an example of+--  extending the default types with your own. type Columns = ColumnUniverse CommonColumns
src/Frames/TH.hs view
@@ -2,6 +2,7 @@              QuasiQuotes, RecordWildCards, RoleAnnotations,              ScopedTypeVariables, TemplateHaskell, TupleSections,              TypeApplications, TypeOperators #-}+{-# LANGUAGE FlexibleContexts #-} -- | Code generation of types relevant to Frames use-cases. Generation -- may be driven by an automated inference process or manual use of -- the individual helpers.@@ -25,6 +26,7 @@ import qualified Pipes as P import qualified Pipes.Prelude as P import qualified Pipes.Safe as P+import Data.Vinyl.CoRec (ShowF)  -- | Generate a column type. recDec :: [Type] -> Type@@ -70,7 +72,7 @@ -- | For each column, we declare a type synonym for its type, and a -- Proxy value of that type. colDec :: T.Text -> String -> T.Text-       -> (Either (String -> Q [Dec]) Type)+       -> Either (String -> Q [Dec]) Type        -> Q (Type, [Dec]) colDec prefix rowName colName colTypeGen = do   (colTy, extraDecs) <- either colDecsHelper (pure . (,[])) colTypeGen@@ -139,8 +141,8 @@  -- -- | Shorthand for a 'Proxy' value of 'ColumnUniverse' applied to the -- -- given type list.--- colQ :: Name -> Q Exp--- colQ n = [e| (Proxy :: Proxy (ColumnUniverse $(conT n))) |]+colQ :: Name -> Q Exp+colQ n = [e| (Proxy :: Proxy (ColumnUniverse $(conT n))) |]  -- | A default 'RowGen'. This instructs the type inference engine to -- get column names from the data file, use the default column@@ -194,7 +196,7 @@ -- | Generate a type for a row of a table all of whose columns remain -- unparsed 'Text' values. tableTypesText' :: forall a c.-                   (c ~ CoRec ColInfo a, ColumnTypeable c, Monoid c)+                   (c ~ CoRec ColInfo a, ColumnTypeable c, Semigroup c)                 => RowGen a -> DecsQ tableTypesText' RowGen {..} =   do colNames <- runIO . P.runSafeT $@@ -228,7 +230,7 @@ -- the CSV file has column names \"foo\", \"bar\", and \"baz\", then -- this will declare @type Foo = "foo" :-> Int@, for example, @foo = -- rlens \@Foo@, and @foo' = rlens' \@Foo@.-tableTypes' :: forall a c. (c ~ CoRec ColInfo a, ColumnTypeable c, Monoid c)+tableTypes' :: forall a c. (c ~ CoRec ColInfo a, ColumnTypeable c, Semigroup c, RPureConstrained (ShowF ColInfo) a)             => RowGen a -> DecsQ tableTypes' (RowGen {..}) =   do headers <- runIO . P.runSafeT@@ -256,3 +258,4 @@           case mColNm of             Just n -> pure (ConT n, []) -- Column's type was already defined             Nothing -> colDec (T.pack tablePrefix) rowTypeName colNm colTy+
test/DataCSV.hs view
@@ -1,56 +1,58 @@-{-# LANGUAGE DeriveLift, OverloadedStrings, TemplateHaskell #-}+{-# LANGUAGE DeriveLift #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}+ module DataCSV where-import Control.Monad ((>=>))+ import Data.Bifunctor (first)-import qualified Data.ByteString as BS-import qualified Data.HashMap.Lazy as H-import Data.Maybe (catMaybes)+import qualified Data.Foldable as F import qualified Data.Text as T import qualified Data.Text.IO as T-import Data.Text.Encoding (decodeUtf8)-import Language.Haskell.TH.Syntax (Lift(..))-import Text.Toml-import Text.Toml.Types (Node (VTable, VString), Table)+import Language.Haskell.TH.Syntax (Lift (..))+import qualified Toml+import Validation -data CsvExample = CsvExample {-  name :: String-  , csv :: String-  , generated :: String }-  deriving Lift+data CsvExample = CsvExample+    { name :: String+    , csv :: String+    , generated :: String+    }+    deriving (Lift, Show) --- instance Lift CsvExample where---   lift (CsvExample n c g) = [e| CsvExample n c g |]+keyText :: Toml.Key -> T.Text+keyText = F.fold . cleanup . map Toml.unPiece . F.toList . Toml.unKey+  where+    -- Top-level table names are parsed as @"name" :|+    -- ["name"]@. Remove that duplication here.+    cleanup [x, y] | x == y = [x]+    cleanup x = x -examplesFrom :: FilePath -> IO [CsvExample]-examplesFrom fp =-  (either error id-   . ((first show . parseTomlDoc "examples") >=> go)-   . decodeUtf8)-  <$> BS.readFile fp-  where go :: Table -> Either String [CsvExample]-        go = fmap catMaybes . mapM (uncurry ex . first T.unpack) . H.toList-        ex :: String -> Node -> Either String (Maybe CsvExample)-        ex k (VTable v) =-          do c <- case H.lookup "csv" v of-                    Nothing -> Right Nothing -- ("No csv key in "++k)-                    Just (VString c) -> Right (Just (T.unpack c))-                    Just _ -> Left ("csv key not a string in " ++ k)-             g <- case H.lookup "generated" v of-                    Nothing -> Left ("No generated key in " ++ k)-                    Just (VString g) -> Right (Just (T.unpack g))-                    Just _ -> Left ("generated key not a string in " ++ k)-             return (CsvExample k <$> c <*> g)-        ex k _ = Left (k ++ " is not a table")+-- | Parse a TOML file that is a top-level table whose values are all+-- the same type. The @tomland@ codec API is centered around starting+-- with a key, but a top-level table does not have a key, so we must+-- use the lower level 'Toml.parse' and 'Toml.tomlTables' before+-- repeatedly applying the provided 'Toml.TomlCodec'.+parseFileOf :: forall a. Toml.TomlCodec a -> T.Text -> Either [T.Text] [(T.Text, a)]+parseFileOf codec =+    first (map Toml.prettyTomlDecodeError)+        . validationToEither+        . traverse (uncurry go)+        . Toml.toList+        . Toml.tomlTables+        . either (error . show) id+        . Toml.parse+  where+    go :: Toml.Key -> Toml.TOML -> Validation [Toml.TomlDecodeError] (T.Text, a)+    go k v = (keyText k,) <$> Toml.runTomlCodec codec v -generatedFrom :: FilePath -> String -> IO String-generatedFrom fp key = (either error id . (>>= go)-                        . first show . parseTomlDoc "examples")-                       <$> T.readFile fp-  where go :: Table -> Either String String-        go toml = do tbl <- case H.lookup (T.pack key) toml of-                              Just (VTable t) -> Right t-                              _ -> Left (key ++ " is not a table")-                     case H.lookup "generated" tbl of-                       Just (VString g) -> Right (T.unpack g)-                       Just _ -> Left ("generated key not a string in " ++ key)-                       Nothing -> Left ("No generated key in " ++ key)+parseExamples :: FilePath -> IO (Either [T.Text] [CsvExample])+parseExamples = fmap (fmap (map mkExample) . parseFileOf exampleCodec) . T.readFile+  where+    exampleCodec = Toml.pair (Toml.string "csv") (Toml.string "generated")+    mkExample (name', (csv', generated')) =+        CsvExample (T.unpack name') csv' generated'++-- | Wraps 'parseExamples' to call 'error' on any parse errors.+examplesFrom :: FilePath -> IO [CsvExample]+examplesFrom = fmap (either (error . show) id) . parseExamples
test/PrettyTH.hs view
@@ -21,7 +21,7 @@  -- | Make template haskell-generated code more readable by -- unqualifying common names, making type operators infix, erasing--- inferrable types, and adding a bit of whitespace.+-- inferable types, and adding a bit of whitespace. makePretty :: String -> String makePretty = -- Add new lines before type synonym definitions              replace' "\ntype " "\n\ntype "@@ -29,13 +29,14 @@              . (!! 10) . iterate (replace infixCons)              . replace infixNil              . replace infixCol-               -- Erase inferrable type+               -- Erase inferable type              . replace ((\x y -> x ++ " ∈ " ++ y)                         <$> ("RElem " *> some (psym (not . isSpace)))                         <*> ((some (psym isSpace) *> some (psym (not . isSpace)))                              <* " (RIndex " <* some (psym (/= ')')) <* ")"))                -- Unqualify names              . replace' "Frames.CSV.ParserOptions" "ParserOptions"+             . replace' "GHC.Maybe." ""              . replace' "GHC.Base." ""              . replace' "GHC.Types." ""              . replace' "Data.Vinyl.Core." ""
test/examples.toml view
@@ -102,72 +102,72 @@                                        Rec g_23 rs_24 -> f_22 (Rec g_23 rs_24) managerId' = rlens' @ManagerId""" -[managers_employees]-generated = """-type ManagerRec = Record [Id, Manager, Age, Pay]-managerRecParser :: ParserOptions-managerRecParser = ParserOptions Nothing (T.pack ",") (Frames.CSV.RFC4180Quoting '"')+# [managers_employees]+# generated = """+# type ManagerRec = Record [Id, Manager, Age, Pay]+# managerRecParser :: ParserOptions+# managerRecParser = ParserOptions Nothing (T.pack ",") (Frames.CSV.RFC4180Quoting '"') -type Id = "id" :-> Int-id :: forall f_0 rs_1 . (Functor f_0, Id ∈ rs_1) =>-                        (Int -> f_0 Bool) -> Record rs_1 -> f_0 (Record rs_1)-id = rlens @Id . rfield-id' :: forall f_2 g_3 rs_4 . (Functor f_2, Functor g_3, Id ∈ rs_4) =>-                             (g_3 Id -> f_2 (g_3 Id)) -> Rec g_3 rs_4 -> f_2 (Rec g_3 rs_4)-id' = rlens' @Id+# type Id = "id" :-> Int+# id :: forall f_0 rs_1 . (Functor f_0, Id ∈ rs_1) =>+#                         (Int -> f_0 Bool) -> Record rs_1 -> f_0 (Record rs_1)+# id = rlens @Id . rfield+# id' :: forall f_2 g_3 rs_4 . (Functor f_2, Functor g_3, Id ∈ rs_4) =>+#                              (g_3 Id -> f_2 (g_3 Id)) -> Rec g_3 rs_4 -> f_2 (Rec g_3 rs_4)+# id' = rlens' @Id -type Manager = "manager" :-> Text-manager :: forall f_5 rs_6 . (Functor f_5, Manager ∈ rs_6) =>-                             (Text -> f_5 Text) -> Record rs_6 -> f_5 (Record rs_6)-manager = rlens @Manager . rfield-manager' :: forall f_7 g_8 rs_9 . (Functor f_7,-                                   Manager ∈ rs_9) =>-                                  (g_8 Manager -> f_7 (g_8 Manager)) -> Rec g_8 rs_9 -> f_7 (Rec g_8 rs_9)-manager' = rlens' @Manager+# type Manager = "manager" :-> Text+# manager :: forall f_5 rs_6 . (Functor f_5, Manager ∈ rs_6) =>+#                              (Text -> f_5 Text) -> Record rs_6 -> f_5 (Record rs_6)+# manager = rlens @Manager . rfield+# manager' :: forall f_7 g_8 rs_9 . (Functor f_7,+#                                    Manager ∈ rs_9) =>+#                                   (g_8 Manager -> f_7 (g_8 Manager)) -> Rec g_8 rs_9 -> f_7 (Rec g_8 rs_9)+# manager' = rlens' @Manager -type Age = "age" :-> Int-age :: forall f_10 rs_11 . (Functor f_10, Age ∈ rs_11) =>-                           (Int -> f_10 Int) -> Record rs_11 -> f_10 (Record rs_11)-age = rlens @Age . rfield-age' :: forall f_12 g_13 rs_14 . (Functor f_12,-                                  Age ∈ rs_14) =>-                                 (g_13 Age -> f_12 (g_13 Age)) -> Rec g_13 rs_14 -> f_12 (Rec g_13 rs_14)-age' = rlens' @Age+# type Age = "age" :-> Int+# age :: forall f_10 rs_11 . (Functor f_10, Age ∈ rs_11) =>+#                            (Int -> f_10 Int) -> Record rs_11 -> f_10 (Record rs_11)+# age = rlens @Age . rfield+# age' :: forall f_12 g_13 rs_14 . (Functor f_12,+#                                   Age ∈ rs_14) =>+#                                  (g_13 Age -> f_12 (g_13 Age)) -> Rec g_13 rs_14 -> f_12 (Rec g_13 rs_14)+# age' = rlens' @Age -type Pay = "pay" :-> Double-pay :: forall f_15 rs_16 . (Functor f_15, Pay ∈ rs_16) =>-                           (Double -> f_15 Double) -> Record rs_16 -> f_15 (Record rs_16)-pay = rlens @Pay . rfield-pay' :: forall f_17 g_18 rs_19 . (Functor f_17,-                                  Pay ∈ rs_19) =>-                                 (g_18 Pay -> f_17 (g_18 Pay)) -> Rec g_18 rs_19 -> f_17 (Rec g_18 rs_19)-pay' = rlens' @Pay+# type Pay = "pay" :-> Double+# pay :: forall f_15 rs_16 . (Functor f_15, Pay ∈ rs_16) =>+#                            (Double -> f_15 Double) -> Record rs_16 -> f_15 (Record rs_16)+# pay = rlens @Pay . rfield+# pay' :: forall f_17 g_18 rs_19 . (Functor f_17,+#                                   Pay ∈ rs_19) =>+#                                  (g_18 Pay -> f_17 (g_18 Pay)) -> Rec g_18 rs_19 -> f_17 (Rec g_18 rs_19)+# pay' = rlens' @Pay -type EmployeeRec = Record ["id" :-> Int, "employee" :-> Text, "age" :-> Int, "pay" :-> Double, "manager_id" :-> Int]-employeeRecParser :: ParserOptions-employeeRecParser = ParserOptions Nothing (T.pack ",") (Frames.CSV.RFC4180Quoting '"')+# type EmployeeRec = Record ["id" :-> Int, "employee" :-> Text, "age" :-> Int, "pay" :-> Double, "manager_id" :-> Int]+# employeeRecParser :: ParserOptions+# employeeRecParser = ParserOptions Nothing (T.pack ",") (Frames.CSV.RFC4180Quoting '"') -type Employee = "employee" :-> Text-employee :: forall f_5 rs_6 . (Functor f_5, Employee ∈ rs_6) =>-                              (Text -> f_5 Text) -> Record rs_6 -> f_5 (Record rs_6)-employee = rlens @Employee . rfield-employee' :: forall f_7 g_8 rs_9 . (Functor f_7,-                                    Employee ∈ rs_9) =>-                                   (g_8 Employee -> f_7 (g_8 Employee)) -> Rec g_8 rs_9 -> f_7 (Rec g_8 rs_9)-employee' = rlens' @Employee+# type Employee = "employee" :-> Text+# employee :: forall f_5 rs_6 . (Functor f_5, Employee ∈ rs_6) =>+#                               (Text -> f_5 Text) -> Record rs_6 -> f_5 (Record rs_6)+# employee = rlens @Employee . rfield+# employee' :: forall f_7 g_8 rs_9 . (Functor f_7,+#                                     Employee ∈ rs_9) =>+#                                    (g_8 Employee -> f_7 (g_8 Employee)) -> Rec g_8 rs_9 -> f_7 (Rec g_8 rs_9)+# employee' = rlens' @Employee -type Age = "age" :-> Int+# type Age = "age" :-> Int -type ManagerId = "manager_id" :-> Int-managerId :: forall f_20 rs_21 . (Functor f_20, ManagerId ∈ rs_21) =>-                                 (Int -> f_20 Int) -> Record rs_21 -> f_20 (Record rs_21)-managerId = rlens @ManagerId . rfield-managerId' :: forall f_22 g_23 rs_24 . (Functor f_22,-                                        ManagerId ∈ rs_24) =>-                                       (g_23 ManagerId -> f_22 (g_23 ManagerId)) ->-                                       Rec g_23 rs_24 -> f_22 (Rec g_23 rs_24)-managerId' = rlens' @ManagerId-"""+# type ManagerId = "manager_id" :-> Int+# managerId :: forall f_20 rs_21 . (Functor f_20, ManagerId ∈ rs_21) =>+#                                  (Int -> f_20 Int) -> Record rs_21 -> f_20 (Record rs_21)+# managerId = rlens @ManagerId . rfield+# managerId' :: forall f_22 g_23 rs_24 . (Functor f_22,+#                                         ManagerId ∈ rs_24) =>+#                                        (g_23 ManagerId -> f_22 (g_23 ManagerId)) ->+#                                        Rec g_23 rs_24 -> f_22 (Rec g_23 rs_24)+# managerId' = rlens' @ManagerId+# """  [double_gt_bool] csv = """