diff --git a/Changelog.md b/Changelog.md
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,3 +1,19 @@
+v0.3.1
+* Added code/cabal bounds (behind “streamly9” flag) for >= streamly-0.9
+* Bumped various upper bounds in cabal file
+
+v0.3.0.0
+* Changed API so tokenizing is handled when the file is read.  This changes various library function signatures.
+
+v0.2.0.0
+* Added support for using any stream type implementing the ```Streaming``` class. Backends are provided for Pipes and Streamly
+* Added ability to infer and load only a subset of columns, identified either by column header or integer column position.
+* Added the ability to selectively infer to a Maybe-like type (```OrMissing```) for any column.
+* Added the ability to rename columns during inference, before the inferred columns are declared.
+* Added the ability to, for inference, use parsers given via a Rec of parsing functions rather than instances of Parseable.
+
+v0.1.1.1
+* Removed Strictness/Memory testing cruft for release.
 v0.1.2.0
 * Made compatible with streamly-0.8.0
 
diff --git a/Frames-streamly.cabal b/Frames-streamly.cabal
--- a/Frames-streamly.cabal
+++ b/Frames-streamly.cabal
@@ -1,13 +1,7 @@
 cabal-version: 2.2
 
--- This file has been generated from package.yaml by hpack version 0.31.2.
---
--- see: https://github.com/sol/hpack
---
--- hash: f129113aa17f26529351746660f5b3ada35ea09fec7866ca91d25902f08ac8be
-
 name:           Frames-streamly
-version:        0.1.2.0
+version:        0.3.1.0
 synopsis:       A streamly layer for Frames I/O
 description:    More information is available in the <https://github.com/adamConnerSax/Frames-streamly/blob/master/Readme.md readme>.'
 category:       Data
@@ -26,6 +20,15 @@
     forestfires.csv
 data-dir:       example_data
 
+flag dump-core
+  description: Dump HTML for the core generated by GHC during compilation
+  default:     False
+
+flag streamly9
+    Description: require streamly >= 0.9
+    Manual: True
+    Default: False
+
 source-repository head
   type: git
   location: https://github.com/adamConnerSax/Frames-streamly
@@ -33,34 +36,67 @@
 library
   exposed-modules:
       Paths_Frames_streamly
+      Frames.Streamly.Categorical
+      Frames.Streamly.ColumnTypeable
+      Frames.Streamly.ColumnUniverse
       Frames.Streamly.CSV
       Frames.Streamly.InCore
+      Frames.Streamly.LoadInCore
+      Frames.Streamly.OrMissing
+      Frames.Streamly.Streaming.Class
+      Frames.Streamly.Streaming.Common
+      Frames.Streamly.Streaming.Streamly
+      Frames.Streamly.Streaming.Pipes
       Frames.Streamly.Transform
+      Frames.Streamly.TH
+  other-modules: Frames.Streamly.Internal.CSV
+
   autogen-modules:
      Paths_Frames_streamly
   hs-source-dirs:
       src
+--  ghc-options: -Wall -O2 -fdicts-strict -fmax-worker-args=16 -fspec-constr-recursive=16 -fplugin=Fusion.Plugin
   ghc-options: -Wall -O2 -fdicts-strict -fmax-worker-args=16 -fspec-constr-recursive=16
   build-depends:
       Frames >=0.6 && <0.8
-    , base >=4.12.0 && <4.17
+    , base >=4.12.0 && <5
+    , bytestring >= 0.10.0 && < 0.12
+    , containers >= 0.6.0 && < 0.7
+    , dlist >= 1.0 && < 1.1
+    , deepseq >= 1.4 && <1.5
     , exceptions >=0.10.0 && <0.11
-    , primitive >=0.7 && <0.8
-    , relude >=1.0.0 && < 1.1
-    , streamly >=0.7 && <0.9
-    , strict >= 0.4 && < 0.5
-    , text >=1.2.3 && <1.3
-    , vinyl >=0.12 && <0.14
+    , fusion-plugin >= 0.2 && < 0.3
+    , foldl >= 1.4.0 && < 1.5
+    , ghc-prim >= 0.6 && < 0.11
+    , monad-control >= 1.0.1 && < 1.1
+    , pipes >= 4.3.14 && <4.4
+    , pipes-safe >= 2.3.1 && < 2.4
+    , pipes-text >= 1.0 && < 1.1
+    , primitive >=0.7 && <0.9
+    , readable >= 0.3 && <0.4
+    , relude >=1.0.0 && < 1.3
+    , strict >= 0.4 && < 0.6
+    , template-haskell >= 2.14 && < 2.21
+    , text >=1.2.3 && <2.2
+    , text-builder >= 0.6.6 && < 0.7
+    , vector-th-unbox >= 0.2.1.3 && <0.3
+    , vector >= 0.12.1.2 && < 0.14
+    , vinyl >=0.12 && <0.15
+    , word8 >= 0.1.3 && < 0.2
+  if flag(streamly9)
+    build-depends:
+      streamly >=0.9 && <0.10, streamly-core >=0.1.0 && <0.2, streamly-bytestring >=0.2.0 && < 0.3
+  else
+    build-depends: streamly >=0.8 && <0.9, streamly-bytestring >=0.1.0 && <0.2
   mixins:  base hiding (Prelude)
-         , relude (Relude as Prelude
-         , Relude.Extra)
+         , relude (Relude as Prelude, Relude.Extra)
   default-language: Haskell2010
 
-test-suite Demo
-  type: exitcode-stdio-1.0
+executable Demo
   main-is: Main.hs
   other-modules:
       DemoPaths
+      DayOfWeek
       Paths_Frames_streamly
   hs-source-dirs:
       examples
@@ -69,7 +105,67 @@
       Frames
     , Frames-streamly
     , base
+    , containers
+    , foldl
+    , readable
+    , relude
     , streamly
+    , template-haskell
     , text
+    , vector
+    , vector-th-unbox
     , vinyl
+  mixins:  base hiding (Prelude)
+         , relude (Relude as Prelude
+         , Relude.Extra)
   default-language: Haskell2010
+
+test-suite FramesStreamlySpec
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules: FramesStreamlySpec
+                 SpecHelper
+                 DemoPaths
+                 Paths_Frames_streamly
+  hs-source-dirs:
+      test
+  ghc-options: -Wall -O2 -static -fdicts-strict -fmax-worker-args=16 -fspec-constr-recursive=16
+  build-depends:
+      Frames
+    , Frames-streamly
+    , hspec
+    , hspec-discover
+    , base
+    , containers
+    , foldl
+    , pipes-safe
+    , streamly
+    , text
+    , vinyl
+  default-language: Haskell2010
+
+benchmark bench-frames-streamly
+  if flag(dump-core)
+    build-depends: dump-core
+    ghc-options: -fforce-recomp -fplugin=DumpCore -fplugin-opt DumpCore:core-html
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      bench
+  other-modules: Paths Paths_Frames_streamly
+--  ghc-options:         -O2 -fspec-constr-recursive=16 -fmax-worker-args=16 -funbox-strict-fields -fplugin=Fusion.Plugin
+  ghc-options:         -O2 -fspec-constr-recursive=16 -fmax-worker-args=16 -funbox-strict-fields -fplugin=Fusion.Plugin
+  main-is:             Bench.hs
+  build-depends:       base
+                     , containers
+                     , criterion
+                     , foldl
+                     , Frames
+                     , Frames-streamly
+                     , fusion-plugin
+                     , pipes
+                     , relude
+                     , text
+                     , vinyl
+  mixins:  base hiding (Prelude)
+         , relude (Relude as Prelude
+         , Relude.Extra)
+  default-language:    Haskell2010
diff --git a/Readme.md b/Readme.md
--- a/Readme.md
+++ b/Readme.md
@@ -1,9 +1,16 @@
-# Frames-streamly- v 0.1.2.0
+# Frames-streamly- v 0.3.1
 
 [![Build Status][travis-badge]][travis]
 [![Hackage][hackage-badge]][hackage]
 [![Hackage Dependencies][hackage-deps-badge]][hackage-deps]
 
+* Added Flag “streamly9”. To use streamly >= 0.9, set that flag to true.
+The flag will put a *lower-bound* of 0.9 on streamly.
+When the flag is false (the default), an upper bound of < 0.9 will be in place
+for streamly. Streamly changed the library structure between 0.8.x and 0.9.x
+in such a way that it is difficult to write this package in a way compatible with
+both sets of versions.
+
 * Breaking Change (from 0.1.0.2):  Some streaming functions
 use the ```StrictReadRec``` class, a stricter version of
 ```ReadRec``` from Frames. This class is located in
@@ -13,6 +20,10 @@
 [Frames](https://hackage.haskell.org/package/Frames)
 package with [streamly](https://hackage.haskell.org/package/streamly).
 
+More generally, it abstracts the streaming layer in Frames into a class
+and implements that class for [Pipes](https://hackage.haskell.org/package/pipes)
+and Streamly.
+
 Frames has some built-in dependencies on the
 [Pipes](https://hackage.haskell.org/package/pipes) package,
 a few of which--primarily file I/O-- require users of Frames to use
@@ -20,8 +31,7 @@
 functionality as Pipes and may be some users preferred streaming
 interface.
 
-This package replicates all the external-facing bits of Frames that
-rely on Pipes and uses streamly instead.  It also fleshes out the Frames
+This package also fleshes out the Frames
 API in a couple of places:
 
 1. It adds some flexibility to the functions to write CSV files.
@@ -60,8 +70,21 @@
 grouped subsets as Frames for memory-efficiency.  These folds
 make that simpler.
 
+4. There is some experimental support for more flexible loading of data
+from CSV. New features include:
+* Choosing of specific columns to load (by position or header text)
+* Renaming of columns before the header text is used to create a column type.
+* Improved handling of type-inference of columns with possibly missing data,
+allowing the user a choice between inference based on non-missing values
+leading to loading failure if missing values are encountered; inferring
+```Maybe a``` where ```a``` is inferred from the non-missing values, thus
+succesfully loading data where some values in the column are missing; or
+an option to choose between the above depending on whether any missing data
+is encountered in the sample Frames uses for inference.
+Please see some examples [here](https://github.com/adamConnerSax/Frames-streamly/blob/master/test/DemoPaths.hs).
 
-An example using some of the utilities is [here](https://github.com/adamConnerSax/Frames-streamly/blob/master/examples/Main.hs).
+
+More examples using some of the utilities is [here](https://github.com/adamConnerSax/Frames-streamly/blob/master/examples/Main.hs).
 _______
 
 LICENSE (BSD-3-Clause)
diff --git a/bench/Bench.hs b/bench/Bench.hs
new file mode 100644
--- /dev/null
+++ b/bench/Bench.hs
@@ -0,0 +1,298 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE PartialTypeSignatures #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeOperators #-}
+module Main where
+
+import Criterion.Main
+import Paths
+
+import qualified Frames.Streamly.CSV as FStreamly
+import qualified Frames.Streamly.InCore as FStreamly
+import qualified Frames.Streamly.LoadInCore as FStreamly
+import qualified Frames.Streamly.TH as FStreamly
+import qualified Frames.Streamly.ColumnUniverse as FStreamly
+import qualified Frames.Streamly.Streaming.Class as Streaming
+import qualified Frames.Streamly.Streaming.Pipes as StreamP
+import qualified Frames.Streamly.Streaming.Streamly as StreamS
+
+import qualified Frames hiding (inCoreAoS)
+import Frames.CSV as Frames
+import Frames.TH as Frames
+import Frames.InCore as Frames
+
+import qualified Data.Vinyl as V
+import qualified Pipes
+import qualified Pipes.Prelude as Pipes
+
+import GHC.TypeLits (KnownSymbol)
+import qualified Control.Foldl as FL
+
+FStreamly.tableTypes' ffNewRowGen
+Frames.tableTypes' ffRowGen
+FStreamly.tableTypes' (ffColSubsetRowGen "forestFires.csv")
+FStreamly.tableTypes' (ffInferTypedSubsetRG  "forestFires.csv")
+
+
+loadAndCountLines :: forall s. Streaming.StreamFunctionsIO s IO => Int -> IO Int
+loadAndCountLines n = do
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let forestFiresPath = forestFiresPathPrefix <> show n <> ".csv"
+  Streaming.runSafe @s $ Streaming.sLength $ Streaming.sReadTextLines @s @IO forestFiresPath
+
+{-
+loadTokenizeRawAndCountCells :: forall s. Streaming.StreamFunctionsIO s IO => Int -> IO Int
+loadTokenizeRawAndCountCells n = do
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let forestFiresPath = forestFiresPathPrefix <> show n <> ".csv"
+  Streaming.runSafe @s $ Streaming.sFolder (+) 0 $ Streaming.sMap length $ Streaming.sTokenizedRaw @s @IO FStreamly.defaultSep forestFiresPath
+-}
+loadTokenizeAndCountCells :: forall s. Streaming.StreamFunctionsIO s IO => Int -> IO Int
+loadTokenizeAndCountCells n = do
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let forestFiresPath = forestFiresPathPrefix <> show n <> ".csv"
+  Streaming.runSafe @s $ Streaming.sFolder (+) 0 $ Streaming.sMap length $ Streaming.sTokenized @s @IO FStreamly.defaultSep FStreamly.NoQuoting  forestFiresPath
+
+loadAndCountRecs :: forall s. Streaming.StreamFunctionsIO s IO => Int -> IO Int
+loadAndCountRecs n = do
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let forestFiresPath = forestFiresPathPrefix <> show n <> ".csv"
+  Streaming.runSafe @s $ Streaming.sLength $ FStreamly.readTableOpt @(Frames.RecordColumns FFNew) @s @IO fFNewParser forestFiresPath
+
+
+loadAndCountFrame :: forall s. Streaming.StreamFunctionsIO s IO => Int -> IO Int
+loadAndCountFrame n = do
+  let fLength = FL.fold FL.length
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let forestFiresPath = forestFiresPathPrefix <> show n <> ".csv"
+  forestFires :: Frames.Frame FFNew <- Streaming.runSafe @s $ FStreamly.inCoreAoS $ FStreamly.readTableOpt @_ @s @IO fFNewParser forestFiresPath
+  return $ fLength forestFires
+
+loadAndCountFrameF :: Int -> IO Int
+loadAndCountFrameF n = do
+  let fLength = FL.fold FL.length
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let forestFiresPath = forestFiresPathPrefix <> show n <> ".csv"
+  forestFires :: Frames.Frame FF <- Frames.runSafeT $ Frames.inCoreAoS $ Frames.readTableOpt fFParser forestFiresPath
+  return $ fLength forestFires
+
+
+loadAndTransform :: forall s. Streaming.StreamFunctionsIO s IO => Int -> IO Int
+loadAndTransform n = do
+  let fLength = FL.fold FL.length
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let forestFiresPath = forestFiresPathPrefix <> show n <> ".csv"
+  forestFires' :: Frames.FrameRec [MthC, DayC, X, Y, AX]  <-
+    Streaming.runSafe @s
+    $ FStreamly.inCoreAoS
+    $ Streaming.sMapMaybe (either (const Nothing) Just . transform)
+    $ FStreamly.readTableOpt @_ @s @IO fFNewParser forestFiresPath
+  return $ fLength forestFires'
+
+loadInCore :: forall s. Streaming.StreamFunctionsIO s IO => Int -> IO Int
+loadInCore n = do
+  let fLength = FL.fold FL.length
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let forestFiresPath = forestFiresPathPrefix <> show n <> ".csv"
+  forestFires' :: Frames.FrameRec [MthC, DayC, X, Y, AX]  <-
+    Streaming.runSafe @s $ FStreamly.loadInCore @s @IO fFNewParser forestFiresPath (either (const Nothing) Just . transform)
+  return $ fLength forestFires'
+
+{-
+loadInCore2 :: forall s. Streaming.StreamFunctionsIO s IO => Int -> IO Int
+loadInCore2 n = do
+  let fLength = FL.fold FL.length
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let forestFiresPath = forestFiresPathPrefix <> show n <> ".csv"
+  forestFires' :: Frames.FrameRec [MthC, DayC, X, Y, AX]  <-
+    Streaming.runSafe @s $ FStreamly.loadInCore2 @s @IO fFNewParser forestFiresPath (either (const Nothing) Just . transform)
+  return $ fLength forestFires'
+-}
+
+loadAndTransformF :: Int -> IO Int
+loadAndTransformF n = do
+  let fLength = FL.fold FL.length
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let forestFiresPath = forestFiresPathPrefix <> show n <> ".csv"
+  forestFires' :: Frames.FrameRec [MthC, DayC, X, Y, AX]  <-
+    Frames.runSafeT
+    $ Frames.inCoreAoS
+    $ Frames.readTableOpt fFParser forestFiresPath Pipes.>-> Pipes.mapMaybe (either (const Nothing) Just . transform)
+  return $ fLength forestFires'
+
+loadSubset :: forall s. Streaming.StreamFunctionsIO s IO => Int -> IO Int
+loadSubset n = do
+  let fLength = FL.fold FL.length
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let forestFiresPath = forestFiresPathPrefix <> show n <> ".csv"
+  forestFires :: Frames.Frame FFColSubset <- Streaming.runSafe @s $ FStreamly.inCoreAoS $ FStreamly.readTableOpt @_ @s @IO fFColSubsetParser forestFiresPath
+  return $ fLength forestFires
+
+
+rcastSubset :: forall s. Streaming.StreamFunctionsIO s IO => Int -> IO Int
+rcastSubset n = do
+  let fLength = FL.fold FL.length
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let forestFiresPath = forestFiresPathPrefix <> show n <> ".csv"
+  forestFires :: Frames.Frame FFColSubset <- Streaming.runSafe @s
+                                             $ FStreamly.inCoreAoS
+                                             $ Streaming.sMap (Frames.rcast @(Frames.RecordColumns FFColSubset))
+                                             $ FStreamly.readTableOpt @(Frames.RecordColumns FF) @s @IO fFNewParser forestFiresPath
+  return $ fLength forestFires
+
+loadTypedSubset :: forall s. Streaming.StreamFunctionsIO s IO => Int -> IO Int
+loadTypedSubset n = do
+  let fLength = FL.fold FL.length
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let forestFiresPath = forestFiresPathPrefix <> show n <> ".csv"
+  forestFires :: Frames.Frame FFInferTyped <- Streaming.runSafe @s $ FStreamly.inCoreAoS $ FStreamly.readTableOpt @_ @s @IO fFInferTypedParser forestFiresPath
+  return $ fLength forestFires
+
+
+loadSubsetAndRetype :: forall s. Streaming.StreamFunctionsIO s IO
+                    => (Frames.Record [X, Y, Month, Day, Wind] -> Maybe (Frames.Record [X, Y, PMonth, PDay, Wind]))
+                    -> Int
+                    -> IO Int
+loadSubsetAndRetype f n = do
+  let fLength = FL.fold FL.length
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let forestFiresPath = forestFiresPathPrefix <> show n <> ".csv"
+  forestFires :: Frames.FrameRec [X,Y,PMonth, PDay, Wind] <- Streaming.runSafe @s
+                                                             $ FStreamly.inCoreAoS
+                                                             $ Streaming.sMapMaybe f
+                                                             $ FStreamly.readTableOpt @_ @s @IO fFColSubsetParser forestFiresPath
+  return $ fLength forestFires
+
+retype1 :: Frames.Record [X, Y, Month, Day, Wind] -> Maybe (Frames.Record [X, Y, PMonth, PDay, Wind])
+retype1 r = do
+  let x = Frames.rgetField @X r
+      y = Frames.rgetField @Y r
+      wind = Frames.rgetField @Wind r
+  pmth <- either (const Nothing) Just $ parseMth $ Frames.rgetField @Month r
+  pday <- either (const Nothing) Just $ parseDayOfWeek $ Frames.rgetField @Day r
+  return $ x Frames.&: y Frames.&: pmth Frames.&: pday Frames.&: wind Frames.&: V.RNil
+
+retype2 :: Frames.Record [X, Y, Month, Day, Wind] -> Maybe (Frames.Record [X, Y, PMonth, PDay, Wind])
+retype2 r = do
+  pmth <- either (const Nothing) Just $ parseMth $ Frames.rgetField @Month r
+  pday <- either (const Nothing) Just $ parseDayOfWeek $ Frames.rgetField @Day r
+  let mkMthDay :: Frames.Record [PMonth, PDay]
+      mkMthDay = pmth Frames.&: pday Frames.&: V.RNil
+  return $ Frames.rcast $ r V.<+> mkMthDay
+
+inferTypes :: forall s b a.(Streaming.StreamFunctionsIO s IO
+                           , Show (FStreamly.ColumnIdType b)
+                           , V.RFoldMap a
+                           , V.RMap a
+                           , V.RApply a)
+           => FStreamly.RowGen s b a -> IO ()
+inferTypes FStreamly.RowGen{..} = do
+  x ::  ([FStreamly.ColTypeInfo (FStreamly.ColType a)], FStreamly.ParseColumnSelector) <-
+    Streaming.runSafe @s
+    $ FStreamly.readColHeaders columnParsers genColumnSelector $ lineReader separator
+  return ()
+
+inferTypesF :: FilePath -> IO ()
+inferTypesF fp = do
+  let lr = Frames.produceTokens fp (Frames.columnSeparator fFParser)
+  _ <- Frames.runSafeT $ readColHeaders @(Frames.CoRec Frames.ColInfo Frames.CommonColumns) fFParser lr
+  return ()
+
+main :: IO ()
+main = do
+  forestFiresPathPrefix <- Paths.usePath Paths.forestFiresPrefix
+  let fp5000 = forestFiresPathPrefix <> "5000.csv"
+  defaultMain [
+{-
+  bgroup "loadAndCountRecs (500)" [ bench "Pipes" $ nfIO (loadAndCountRecs @StreamP.PipeStream 500)
+                              , bench "Streamly" $ nfIO (loadAndCountRecs @(FStreamly.DefaultStream) 500)
+                              , bench "Frames" $ nfIO (loadAndCountRecsF 500)
+                              ]
+  , bgroup "loadAndTransform (500)" [ bench "Pipes" $ nfIO (loadAndTransform @StreamP.PipeStream 500)
+                                    , bench "Streamly" $ nfIO (loadAndTransform @(FStreamly.DefaultStream) 500)
+                                    , bench "Frames" $ nfIO (loadAndTransformF 500)
+                              ]
+
+-}
+
+    bgroup "inference (1000/5000)" [ bench "Pipes" $ nfIO (inferTypes $ ffNewRowGenP fp5000)
+                                   , bench "Streamly" $ nfIO (inferTypes $ ffNewRowGenS fp5000)
+                                   , bench "Frames" $ nfIO $ inferTypesF fp5000
+                                   , bench "Pipes/subset" $ nfIO $ inferTypes $ ffColSubsetRowGen "forestFires5000.csv"
+                                   ]
+
+    , bgroup "loadAndCountLines (5000)" [ bench "Pipes" $ nfIO $ loadAndCountLines @StreamP.PipeStream 5000
+                                        , bench "Streamly" $ nfIO $ loadAndCountLines @(FStreamly.DefaultStream) 5000]
+    , bgroup "loadTokenizeAndCountCells (5000)" [ bench "Pipes" $ nfIO $ loadTokenizeAndCountCells @StreamP.PipeStream 5000
+                                                , bench "Streamly" $ nfIO $ loadTokenizeAndCountCells @(FStreamly.DefaultStream) 5000]
+
+    , bgroup "loadAndCountRecs (5000)" [ bench "Pipes" $ nfIO (loadAndCountRecs @StreamP.PipeStream 5000)
+                                   , bench "Streamly" $ nfIO (loadAndCountRecs @(FStreamly.DefaultStream) 5000)
+                                   ]
+    , bgroup "loadAndCountFrame (5000)" [ bench "Pipes" $ nfIO (loadAndCountFrame @StreamP.PipeStream 5000)
+                                        , bench "Streamly" $ nfIO (loadAndCountFrame @(FStreamly.DefaultStream) 5000)
+                                        , bench "Frames" $ nfIO (loadAndCountFrameF 5000)
+                                        ]
+    , bgroup "loadAndTransform (5000)" [ bench "Pipes" $ nfIO (loadAndTransform @StreamP.PipeStream 5000)
+                                       , bench "Streamly" $ nfIO (loadAndTransform @(FStreamly.DefaultStream) 5000)
+                                       , bench "Frames" $ nfIO (loadAndTransformF 5000)
+                                       ]
+    , bgroup "loadInCore (5000)" [ bench "Pipes" $ nfIO (loadInCore @StreamP.PipeStream 5000)
+                                 , bench "Streamly" $ nfIO (loadInCore @(FStreamly.DefaultStream) 5000)
+                                 ]
+{-
+    , bgroup "loadInCore2 (5000)" [ bench "Pipes" $ nfIO (loadInCore2 @StreamP.PipeStream 5000)
+                                  , bench "Streamly" $ nfIO (loadInCore2 @(FStreamly.DefaultStream) 5000)
+                                  ]
+  , bgroup "loadAndCountRecs (50000)" [ bench "Pipes" $ nfIO (loadAndCountRecs @StreamP.PipeStream 50000)
+                                  , bench "Streamly" $ nfIO (loadAndCountRecs @(FStreamly.DefaultStream) 50000)
+                                  , bench "Frames" $ nfIO (loadAndCountRecsF 50000)
+                                  ]
+  , bgroup "loadAndTransform (50000)" [ bench "Pipes" $ nfIO (loadAndTransform @StreamP.PipeStream 50000)
+                                      , bench "Streamly" $ nfIO (loadAndTransform @(FStreamly.DefaultStream) 50000)
+                                      , bench "Frames" $ nfIO (loadAndTransformF 50000)
+                                    ]
+-}
+    , bgroup "colSubset (5000)" [ bench "Pipes/load-subset" $ nfIO (loadSubset @StreamP.PipeStream 5000)
+                                , bench "Pipes/rcast" $ nfIO (rcastSubset @StreamP.PipeStream 5000)
+                                , bench "Streamly/load-subset" $ nfIO (loadSubset @(FStreamly.DefaultStream) 5000)
+                                , bench "Streamly/rcast" $ nfIO (rcastSubset @(FStreamly.DefaultStream) 5000)
+                                ]
+
+    , bgroup "custom-parsing (5000)" [ bench "Pipes/load-parsed" $ nfIO (loadTypedSubset @StreamP.PipeStream 5000)
+                                     , bench "Pipes/parseAfter v1" $ nfIO (loadSubsetAndRetype @StreamP.PipeStream retype1 5000)
+                                     , bench "Pipes/parseAfter v2" $ nfIO (loadSubsetAndRetype @StreamP.PipeStream retype2 5000)
+                                     , bench "Streamly/load-parsed" $ nfIO (loadTypedSubset @(FStreamly.DefaultStream) 5000)
+                                     , bench "Streamly/parseAfter v1" $ nfIO (loadSubsetAndRetype @(FStreamly.DefaultStream) retype1 5000)
+                                     , bench "Streamly/parseAfter v2" $ nfIO (loadSubsetAndRetype @(FStreamly.DefaultStream) retype2 5000)
+                                     ]
+
+
+  {-
+  , bgroup "loadAndCountRecs (500000)" [ bench "Pipes" $ nfIO (loadAndCountRecs @StreamP.PipeStream 500000)
+                                   , bench "Streamly" $ nfIO (loadAndCountRecs @(FStreamly.DefaultStream) 500000)
+                                   , bench "Frames" $ nfIO (loadAndCountRecsF 500000)
+                                   ]
+-}
+    ]
+
+-- | Create a record with one field from a value.  Use a TypeApplication to choose the field.
+recordSingleton :: forall af s a. (KnownSymbol s, af ~ '(s,a)) => a -> Frames.Record '[af]
+recordSingleton a = a Frames.&: V.RNil
+{-# INLINE recordSingleton #-}
+
+type AX = "AX" Frames.:-> Double
+
+transform :: Frames.Record [X,Y,Month,Day,Temp,Wind] -> Either Text (Frames.Record [MthC, DayC, X, Y, AX])
+transform r = do
+  let ax = recordSingleton @AX $ Frames.rgetField @Wind r + Frames.rgetField @Temp r
+  day <- fmap (recordSingleton @DayC) . parseDayOfWeek $ Frames.rgetField @Day r
+  mth <- fmap (recordSingleton @MthC) . parseMth $  Frames.rgetField @Month r
+  return $ Frames.rcast $ r V.<+> ax V.<+> day V.<+> mth
+{-# INLINEABLE transform #-}
diff --git a/bench/Paths.hs b/bench/Paths.hs
new file mode 100644
--- /dev/null
+++ b/bench/Paths.hs
@@ -0,0 +1,148 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE NoMonomorphismRestriction #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeOperators #-}
+
+module Paths where
+
+import qualified Paths_Frames_streamly as Paths
+import qualified Frames.Streamly.TH as FStreamly
+import qualified Frames.Streamly.CSV as FStreamly hiding (quotingMode)
+import qualified Frames.Streamly.ColumnTypeable as FStreamly
+import qualified Frames.Streamly.ColumnUniverse as FStreamly
+import qualified Frames.Streamly.OrMissing as FStreamly
+import Frames.Streamly.Streaming.Class (sTokenized)
+import qualified Frames.Streamly.Streaming.Pipes as StreamP
+import qualified Frames.Streamly.Streaming.Streamly as StreamS
+
+import qualified Data.Set as Set
+import qualified Data.Map as Map
+import qualified Data.Text as T
+import qualified Data.Vinyl as V
+import qualified Frames
+import qualified Frames.TH as Frames
+
+forestFiresPrefix :: FilePath
+forestFiresPrefix = "forestFires"
+
+forestFiresPath :: FilePath
+forestFiresPath = "forestFires.csv"
+
+forestFiresNoHeaderPath :: FilePath
+forestFiresNoHeaderPath = "forestFiresNoHeader.csv"
+
+forestFiresFewerColsPath :: FilePath
+forestFiresFewerColsPath = "forestFiresFewerCols.csv"
+
+thPath :: FilePath -> FilePath
+thPath x = "./example_data/" ++ x
+
+usePath :: FilePath -> IO FilePath
+usePath x =  fmap (\dd -> dd ++ "/" ++ x) Paths.getDataDir
+
+
+ffRowGen :: Frames.RowGen Frames.CommonColumns
+ffRowGen = (Frames.rowGen (thPath forestFiresPath)) { Frames.rowTypeName = "FF" }
+
+ffNewRowGen :: FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByName Frames.CommonColumns
+ffNewRowGen = (FStreamly.rowGen (thPath forestFiresPath)) { FStreamly.rowTypeName = "FFNew" }
+
+ffNewRowGenP :: FilePath -> FStreamly.RowGen StreamP.PipeStream 'FStreamly.ColumnByName Frames.CommonColumns
+ffNewRowGenP fp = rg  { FStreamly.rowTypeName = "FFNew"
+                      , FStreamly.lineReader = \sep -> sTokenized sep (FStreamly.quotingMode rg) fp --FStreamly.streamTokenized' @StreamP.PipeStream @IO fp
+                      }
+  where
+    rg = FStreamly.rowGen (thPath forestFiresPath)
+
+
+ffNewRowGenS :: FilePath -> FStreamly.RowGen (FStreamly.DefaultStream) 'FStreamly.ColumnByName Frames.CommonColumns
+ffNewRowGenS fp = rg
+                  { FStreamly.rowTypeName = "FFNew"
+                  , FStreamly.lineReader = \sep -> sTokenized sep (FStreamly.quotingMode rg) fp --FStreamly.streamTokenized' @(StreamS.StreamlyStream StreamS.SerialT) @IO fp
+                  }
+  where
+    rg = FStreamly.rowGen (thPath forestFiresPath)
+
+ffColSubsetRowGen :: FilePath -> FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByName Frames.CommonColumns
+ffColSubsetRowGen fp = FStreamly.modifyColumnSelector modSelector rowGen
+  where
+    rowTypeName = "FFColSubset"
+    rowGen = (FStreamly.rowGen (thPath fp)) { FStreamly.rowTypeName = rowTypeName }
+    modSelector = FStreamly.columnSubset (Set.fromList $ fmap FStreamly.HeaderText ["X","Y","month","day","temp"])
+
+data Mth = Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec deriving (Enum, Bounded)
+FStreamly.derivingOrMissingUnboxVectorFor'
+  (FStreamly.derivingUnbox  "Mth" [t|Mth -> Word8|] [e|fromIntegral . fromEnum|] [e|toEnum . fromIntegral|])
+  "Mth"
+  [e|Jan|]
+type MthC = "Month" Frames.:-> Mth
+
+parseMth :: Text -> Either Text Mth
+parseMth "jan" = Right Jan
+parseMth "feb" = Right Feb
+parseMth "mar" = Right Mar
+parseMth "apr" = Right Apr
+parseMth "may" = Right May
+parseMth "jun" = Right Jun
+parseMth "jul" = Right Jul
+parseMth "aug" = Right Aug
+parseMth "sep" = Right Sep
+parseMth "oct" = Right Oct
+parseMth "nov" = Right Nov
+parseMth "dec" = Right Dec
+parseMth x = Left x
+
+instance FStreamly.Parseable Mth where
+  parse t = case parseMth t of
+    Left _ -> mzero
+    Right p -> return $ FStreamly.Definitely p
+
+data DayOfWeek = Mon | Tue | Wed | Thu | Fri | Sat | Sun deriving (Enum, Bounded)
+FStreamly.derivingOrMissingUnboxVectorFor'
+  (FStreamly.derivingUnbox  "DayOfWeek" [t|DayOfWeek -> Word8|] [e|fromIntegral . fromEnum|] [e|toEnum . fromIntegral|])
+  "DayOfWeek"
+  [e|Mon|]
+
+type DayC = "Day" Frames.:-> DayOfWeek
+
+parseDayOfWeek :: Text -> Either Text DayOfWeek
+parseDayOfWeek "mon" = Right Mon
+parseDayOfWeek "tue" = Right Tue
+parseDayOfWeek "wed" = Right Wed
+parseDayOfWeek "thu" = Right Thu
+parseDayOfWeek "fri" = Right Fri
+parseDayOfWeek "sat" = Right Sat
+parseDayOfWeek "sun" = Right Sun
+parseDayOfWeek x = Left x
+
+instance FStreamly.Parseable DayOfWeek where
+  parse t = case parseDayOfWeek t of
+    Left _ -> mzero
+    Right p -> return $ FStreamly.Definitely p
+
+type ParsedCols = [Bool, Int, Double, Mth, DayOfWeek, Text]
+
+
+dayMonthColsParserHowRec :: FStreamly.ParseHowRec ParsedCols
+dayMonthColsParserHowRec = FStreamly.parseableParseHowRec
+{-  let pph = FStreamly.parseableParseHow
+  in pph
+     V.:& pph
+     V.:& pph
+     V.:& FStreamly.simpleParseHow (either (const Nothing) Just . parseMth)
+     V.:& FStreamly.simpleParseHow (either (const Nothing) Just . parseDayOfWeek)
+     V.:& pph
+     V.:& V.RNil
+-}
+ffInferTypedSubsetRG :: FilePath -> FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByName ParsedCols
+ffInferTypedSubsetRG fp = (ffColSubsetRowGen fp) { FStreamly.columnParsers = dayMonthColsParserHowRec
+                                                 , FStreamly.tablePrefix ="P"
+                                                 , FStreamly.rowTypeName = "FFInferTyped"
+                                                 }
diff --git a/examples/DayOfWeek.hs b/examples/DayOfWeek.hs
new file mode 100644
--- /dev/null
+++ b/examples/DayOfWeek.hs
@@ -0,0 +1,39 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module DayOfWeek where
+
+import qualified Frames.Streamly.ColumnTypeable as FStreamly
+import Frames.Streamly.OrMissing -- for derivingOrMissingUnboxVectorFor' and required in scope imports
+import qualified Data.Readable as Readable
+
+data DayOfWeek = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday deriving (Show, Eq, Enum, Bounded)
+
+
+-- This derives Unbox and VectorFor for DayOfWeek and (OrMissing DayOfWeek)
+derivingOrMissingUnboxVectorFor'
+  (derivingUnbox
+    "DayOfWeek"
+    [t|DayOfWeek -> Word8|]
+    [e|fromIntegral . fromEnum|]
+    [e|toEnum . fromIntegral|])
+  "DayOfWeek"
+  [e|Monday|]
+
+instance Readable.Readable DayOfWeek where
+  fromText "mon" = return Monday
+  fromText "tue" = return Tuesday
+  fromText "wed" = return Wednesday
+  fromText "thu" = return Thursday
+  fromText "fri" = return Friday
+  fromText "sat" = return Saturday
+  fromText "sun" = return Sunday
+  fromText _ = mzero
+
+instance FStreamly.Parseable DayOfWeek where
+  parse = fmap FStreamly.Definitely . Readable.fromText
diff --git a/examples/DemoPaths.hs b/examples/DemoPaths.hs
--- a/examples/DemoPaths.hs
+++ b/examples/DemoPaths.hs
@@ -1,13 +1,191 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE NoMonomorphismRestriction #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeFamilies #-}
+
 module DemoPaths where
 
+import DayOfWeek
+
 import qualified Paths_Frames_streamly as Paths
+import qualified Frames.Streamly.TH as FStreamly
+import qualified Frames.Streamly.CSV as FStreamly
+import qualified Frames.Streamly.ColumnUniverse as FStreamly
+import qualified Frames.Streamly.ColumnTypeable as FStreamly
+import Frames.Streamly.OrMissing
+import qualified Data.Set as Set
+import Data.Vinyl as V
 
 forestFiresPath :: FilePath
 forestFiresPath = "forestfires.csv"
 
+forestFiresMissingPath :: FilePath
+forestFiresMissingPath = "forestfiresMissing.csv"
+
+cesPath :: FilePath
+cesPath = "CES_short.csv"
+
 thPath :: FilePath -> FilePath
 thPath x = "./example_data/" ++ x
 
-
 usePath :: FilePath -> IO FilePath
 usePath x =  fmap (\dd -> dd ++ "/" ++ x) Paths.getDataDir
+
+ffBaseRowGen :: FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByName FStreamly.CommonColumns
+ffBaseRowGen = (FStreamly.rowGen (thPath forestFiresPath)) { FStreamly.rowTypeName = "ForestFires", FStreamly.tablePrefix = "FF" }
+
+ffColSubsetRowGen :: FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByName FStreamly.CommonColumns
+ffColSubsetRowGen = FStreamly.modifyColumnSelector modSelector rowGen
+  where
+    rowTypeName = "FFColSubset"
+    rowGen = (FStreamly.rowGen (thPath forestFiresPath)) { FStreamly.rowTypeName = rowTypeName, FStreamly.tablePrefix = "CS" }
+    modSelector = FStreamly.columnSubset (Set.fromList $ fmap FStreamly.HeaderText ["X","Y","month","day","temp","wind"])
+
+ffColSubsetRowGenCat :: FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByName FStreamly.CommonColumnsCat
+ffColSubsetRowGenCat = FStreamly.modifyColumnSelector modSelector rowGen
+  where
+    rowTypeName = "FFColSubsetCat"
+    rowGen = (FStreamly.rowGenCat (thPath forestFiresPath)) { FStreamly.rowTypeName = rowTypeName, FStreamly.tablePrefix = "Cat" }
+    modSelector = FStreamly.columnSubset (Set.fromList $ fmap FStreamly.HeaderText ["X","Y","month","day","temp","wind"])
+
+ffInferOrMissingRG :: FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByName FStreamly.CommonColumns
+ffInferOrMissingRG = setOrMissingWhen $ FStreamly.modifyColumnSelector modSelector rowGen
+  where
+    setOrMissingWhen = FStreamly.setOrMissingWhen (FStreamly.HeaderText "wind") FStreamly.IfSomeMissing
+    modSelector = FStreamly.columnSubset (Set.fromList $ fmap FStreamly.HeaderText ["X","Y","month","day","temp","wind"])
+    rowGen = (FStreamly.rowGen (thPath forestFiresMissingPath)) {
+      FStreamly.rowTypeName = "FFInferOrMissing"
+      , FStreamly.tablePrefix = "IM"
+      }
+
+
+ffInferOrMissingCatRG :: FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByName FStreamly.CommonColumnsCat
+ffInferOrMissingCatRG = setOrMissingWhen $ FStreamly.modifyColumnSelector modSelector rowGen
+  where
+    setOrMissingWhen = FStreamly.setOrMissingWhen (FStreamly.HeaderText "wind") FStreamly.IfSomeMissing
+                   . FStreamly.setOrMissingWhen (FStreamly.HeaderText "day") FStreamly.IfSomeMissing
+    modSelector = FStreamly.columnSubset (Set.fromList $ fmap FStreamly.HeaderText ["X","Y","month","day","temp","wind"])
+    rowGen = (FStreamly.rowGenCat (thPath forestFiresMissingPath)) {
+      FStreamly.rowTypeName = "FFInferOrMisingCat"
+      , FStreamly.tablePrefix = "IMC"
+      }
+
+
+type TDColumns = [Bool, Int, Double, DayOfWeek, Text]
+
+ffInferTypedDayRG :: FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByName TDColumns
+ffInferTypedDayRG = FStreamly.modifyColumnSelector modSelector rg
+  where
+    rg = FStreamly.RowGen
+         FStreamly.allColumnsAsNamed
+         "TD"
+         FStreamly.defaultSep
+         FStreamly.defaultQuotingMode
+         "FFInferTypedDay"
+         FStreamly.parseableParseHowRec
+         1000
+         FStreamly.defaultIsMissing
+         (\sep -> FStreamly.sTokenized @_ @IO sep FStreamly.defaultQuotingMode (thPath forestFiresPath))
+    modSelector = FStreamly.columnSubset (Set.fromList $ fmap FStreamly.HeaderText ["X","Y","month","day","temp","wind"])
+
+ffInferTypedDayOrMissingRG :: FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByName TDColumns
+ffInferTypedDayOrMissingRG = setOrMissingWhen
+                              $ ffInferTypedDayRG { FStreamly.rowTypeName = "FFInferTypedDayOrMissing"
+                                                  , FStreamly.tablePrefix = "TDOM"
+                                                  , FStreamly.lineReader = \sep -> FStreamly.sTokenized sep FStreamly.defaultQuotingMode (thPath forestFiresPath)
+
+                                                  } where
+  setOrMissingWhen = FStreamly.setOrMissingWhen (FStreamly.HeaderText "wind") FStreamly.IfSomeMissing
+                     . FStreamly.setOrMissingWhen (FStreamly.HeaderText "day") FStreamly.IfSomeMissing
+
+
+data Month = Jan | Feb | Mar | Apr | May | Jun | Jul | Aug | Sep | Oct | Nov | Dec deriving (Show, Read, Eq, Enum, Bounded)
+
+FStreamly.derivingOrMissingUnboxVectorFor'
+  (derivingUnbox
+    "Month"
+    [t|Month -> Word8|]
+    [e|fromIntegral . fromEnum|]
+    [e|toEnum . fromIntegral|])
+  "Month"
+  [e|Jan|]
+
+
+parseMonthLower :: Text -> Maybe Month
+parseMonthLower = \case
+  "jan" -> Just Jan
+  "feb" -> Just Feb
+  "mar" -> Just Mar
+  "apr" -> Just Apr
+  "may" -> Just May
+  "jun" -> Just Jun
+  "jul" -> Just Jul
+  "aug" -> Just Aug
+  "sep" -> Just Sep
+  "oct" -> Just Oct
+  "nov" -> Just Nov
+  "dec" -> Just Dec
+  _ -> Nothing
+
+type DayMonthCols = [Bool, Int, Double, DayOfWeek, Month, Text]
+
+dayMonthColsParserHowRec :: FStreamly.ParseHowRec DayMonthCols
+dayMonthColsParserHowRec =
+  let pph = FStreamly.parseableParseHow
+  in pph V.:& pph V.:& pph V.:& pph V.:& FStreamly.simpleParseHow parseMonthLower V.:& pph V.:& V.RNil
+
+ffInferTypedDayMonthRG :: FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByName DayMonthCols
+ffInferTypedDayMonthRG = FStreamly.modifyColumnSelector modSelector rg
+  where
+    rg = FStreamly.RowGen
+         FStreamly.allColumnsAsNamed
+         "TDM"
+         FStreamly.defaultSep
+         FStreamly.defaultQuotingMode
+         "FFInferTypedDayMonth"
+         dayMonthColsParserHowRec
+         1000
+         FStreamly.defaultIsMissing
+         (\sep -> FStreamly.sTokenized @_ @IO sep FStreamly.defaultQuotingMode (thPath forestFiresPath))
+    modSelector = FStreamly.columnSubset (Set.fromList $ fmap FStreamly.HeaderText ["X","Y","month","day","temp","wind"])
+
+
+{-
+cesCols :: Set.Set FStreamly.HeaderText
+cesCols = Set.fromList (FStreamly.HeaderText <$> ["year"
+                                                 , "case_id"
+                                                 , "weight"
+                                                 , "weight_cumulative"
+                                                 , "st"
+                                                 , "dist_up"
+                                                 , "gender"
+                                                 , "age"
+                                                 , "educ"
+                                                 , "race"
+                                                 , "hispanic"
+                                                 , "pid3"
+                                                 , "pid7"
+                                                 , "pid3_leaner"
+                                                 , "vv_regstatus"
+                                                 , "vv_turnout_gvm"
+                                                 , "voted_rep_party"
+                                                 , "voted_pres_08"
+                                                 , "voted_pres_12"
+                                                 , "voted_pres_16"
+                                                 , "voted_pres_20"
+                                                 ])
+
+cesRowGenAllCols = (FStreamly.rowGen (thPath cesPath)) { FStreamly.tablePrefix = "CCES"
+                                                       , FStreamly.separator   = ","
+                                                       , FStreamly.rowTypeName = "CCES"
+                                                       }
+
+cesRowGen = FStreamly.modifyColumnSelector colSubset cesRowGenAllCols where
+  colSubset = FStreamly.columnSubset cesCols
+-}
diff --git a/examples/Main.hs b/examples/Main.hs
--- a/examples/Main.hs
+++ b/examples/Main.hs
@@ -1,31 +1,60 @@
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE NoMonomorphismRestriction #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeOperators #-}
 {-# LANGUAGE TemplateHaskell #-}
 module Main where
 
 import qualified DemoPaths as Paths
+import DemoPaths (Month)
+import DayOfWeek
 import qualified Frames
+--import Frames.Streamly.Categorical ()
 import qualified Frames.Streamly.CSV as FStreamly
 import qualified Frames.Streamly.InCore as FStreamly
 import qualified Frames.Streamly.Transform as FStreamly
+import qualified Frames.Streamly.TH as FStreamly
+import Frames.Streamly.Streaming.Class (runSafe)
+import Frames.Streamly.Streaming.Streamly (StreamlyStream)
+import Frames.Streamly.ColumnTypeable ()
 import qualified Data.Vinyl as V
+import qualified Data.Vinyl.Functor as V
 import qualified Text.Printf as Printf
-import qualified Streamly.Prelude as Streamly
-import Data.Text (Text) -- for Frames template splicing 
+--import qualified Streamly.Prelude as Streamly
+import Data.Text (Text) -- for Frames template splicing
 import qualified Data.Text as Text
 
-Frames.tableTypes "ForestFires" (Paths.thPath Paths.forestFiresPath)
+import qualified Control.Foldl as FL
+import Data.List (intercalate)
+import DemoPaths (cesPath)
 
+FStreamly.tableTypes' Paths.ffBaseRowGen
+FStreamly.tableTypes' Paths.ffColSubsetRowGen
+FStreamly.tableTypes' Paths.ffColSubsetRowGenCat
+--FStreamly.tableTypes' Paths.cesRowGen
+FStreamly.tableTypes' Paths.ffInferOrMissingRG
+FStreamly.tableTypes' Paths.ffInferOrMissingCatRG
+FStreamly.tableTypes' Paths.ffInferTypedDayRG
+FStreamly.tableTypes' Paths.ffInferTypedDayOrMissingRG
+FStreamly.tableTypes' Paths.ffInferTypedDayMonthRG
+
+readTableOpt = FStreamly.readTableOpt @_ @FStreamly.DefaultStream
+
 main :: IO ()
 main = do
-  forestFiresPath <- Paths.usePath Paths.forestFiresPath 
-  forestFires :: Frames.Frame ForestFires <- FStreamly.inCoreAoS $ FStreamly.readTable forestFiresPath
-  let filterAndMap :: ForestFires -> Maybe (Frames.Record [X, Y, Month, Day, Temp, Wind])
-      filterAndMap r = if Frames.rgetField @Day r == "fri" then (Just $ Frames.rcast r) else Nothing
+  let rSafe = runSafe @FStreamly.DefaultStream
+  forestFiresPath <- Paths.usePath Paths.forestFiresPath
+  forestFires :: Frames.Frame ForestFires <- rSafe $ FStreamly.inCoreAoS $ FStreamly.readTableOpt @_ @FStreamly.DefaultStream @IO forestFiresParser forestFiresPath
+  forestFiresColSubset :: Frames.Frame FFColSubset <- rSafe $ FStreamly.inCoreAoS $ readTableOpt fFColSubsetParser forestFiresPath
+  let filterAndMap :: ForestFires -> Maybe (Frames.Record [FFX, FFY, FFMonth, FFDay, FFTemp, FFWind])
+      filterAndMap r = if Frames.rgetField @FFDay r == "fri" then (Just $ Frames.rcast r) else Nothing
       forestFires' = FStreamly.mapMaybe filterAndMap forestFires
+      forestFiresColSubset' = FStreamly.filter  (\r -> Frames.rgetField @FFDay r == "fri") forestFiresColSubset
       formatRow = FStreamly.formatWithShow
               V.:& FStreamly.formatWithShow
               V.:& FStreamly.formatTextAsIs
@@ -33,8 +62,31 @@
               V.:& FStreamly.liftFieldFormatter (Text.pack . Printf.printf "%.1f")
               V.:& FStreamly.liftFieldFormatter (Text.pack . Printf.printf "%.1f")
               V.:& V.RNil
-      csvTextStream = FStreamly.streamSV' formatRow "," $ Streamly.fromFoldable forestFires'
-  Streamly.toList csvTextStream >>= putStrLn . Text.unpack . Text.intercalate "\n"
-  FStreamly.writeLines "exampleOut.csv" csvTextStream
-  
+      csvTextStream = FStreamly.streamSV' formatRow "," $ FStreamly.foldableToStream @_ @FStreamly.DefaultStream forestFires'
+--      csvTextStreamCS = FStreamly.streamSV' formatRow "," $ Streamly.fromFoldable forestFiresColSubset'
 
+--  putStrLn $ intercalate "\n" $ fmap show $ FL.fold FL.list forestFiresColSubset'
+--  Streamly.toList csvTextStream >>= putStrLn . Text.unpack . Text.intercalate "\n"
+  FStreamly.writeLines "exampleOut.csv" csvTextStream
+--  FStreamly.writeLines "exampleOutCS.csv" csvTextStreamCS
+  forestFiresMissingPath <- Paths.usePath Paths.forestFiresMissingPath
+  -- try to load with ordinary row
+  let tableLength :: Foldable f => f a -> Int
+      tableLength = FL.fold FL.length
+  forestFiresMissing :: Frames.Frame FFColSubset <- rSafe $ FStreamly.inCoreAoS $ readTableOpt fFColSubsetParser forestFiresMissingPath
+  putStrLn $ "Loaded table with missing data using inferred row from complete table. Complete table has "
+    <> show (tableLength forestFires)
+    <> " and with missing data (1 row missing 'day' a Text entry, one missing 'wind', a Double) has "
+    <> show (tableLength forestFiresMissing)
+  forestFiresMissing2 :: Frames.Frame FFInferOrMissing <- rSafe $ FStreamly.inCoreAoS $ readTableOpt fFInferOrMissingParser forestFiresMissingPath
+  putStrLn $ "Loaded the same table but with the types for 'day' and 'wind' set to OrMissing. Has "
+    <> show (tableLength forestFiresMissing2)
+    <> " rows."
+  forestFiresTypedDay :: Frames.Frame FFInferTypedDay <- rSafe $ FStreamly.inCoreAoS $ readTableOpt fFInferTypedDayParser forestFiresMissingPath
+  putStrLn $ "Loaded complete table with a DayOfWeek type added. Has "
+    <> show (tableLength forestFiresTypedDay)
+    <> " rows."
+  forestFiresTypedDayOM :: Frames.Frame FFInferTypedDayOrMissing <- rSafe $ FStreamly.inCoreAoS $ readTableOpt fFInferTypedDayOrMissingParser forestFiresMissingPath
+  putStrLn $ "Loaded table with missing data, a DayOfWeek type added and relevant columns set to OrMissing if some missing. Has "
+    <> show (tableLength forestFiresTypedDayOM)
+    <> " rows."
diff --git a/src/Frames/Streamly/CSV.hs b/src/Frames/Streamly/CSV.hs
--- a/src/Frames/Streamly/CSV.hs
+++ b/src/Frames/Streamly/CSV.hs
@@ -3,15 +3,18 @@
 {-# LANGUAGE ConstraintKinds #-}
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveLift #-}
 {-# LANGUAGE DerivingStrategies #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE PolyKinds #-}
 {-# LANGUAGE RankNTypes        #-}
 {-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StandaloneDeriving #-}
 {-# LANGUAGE StrictData #-}
 {-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeApplications #-}
@@ -19,7 +22,7 @@
 {-# LANGUAGE TypeOperators #-}
 {-# LANGUAGE UndecidableInstances #-}
 {-# LANGUAGE UndecidableSuperClasses #-}
---{-# OPTIONS_GHC -O0 #-}
+{-# LANGUAGE RecordWildCards #-}
 {-|
 Module      : Frames.Streamly.CSV
 Description : CSV parsing/formatting tools for the Frames library, operating via streamly Streams.
@@ -32,11 +35,20 @@
 This module adds some functionality for formatting in more flexible ways than the pipes version in Frames.
 It allows us of Show instances, in addition to the ShowCSV class included in Frames.  And it allows one-off
 specification of a format as well.  See the example for more details.
+
+There is also support for parsing only a subset of columns of a file, which can save time and memory during
+loading and parsing of text. See the readme or Frames.Streamly.TH for more details.
 -}
 module Frames.Streamly.CSV
     (
+      -- * Parsing
+      ParserOptions(..)
+    , defaultParser
+    , textToSeparator
+    , separatorToText
+    , useRowFilter
       -- * Strict version of ReadRec
-      StrictReadRec (..)
+    , StrictReadRec (..)
       -- * read from File to Stream of Recs
     , readTable
     , readTableOpt
@@ -58,6 +70,7 @@
     , streamSV
     , streamSV'
       -- *  write Records to Text File
+    , writeLines
     , writeCSV
     , writeSV
     , writeStreamSV
@@ -66,47 +79,56 @@
     , writeStreamSV_Show
       -- * Utilities
     , streamToList
+    , foldableToStream
     , liftFieldFormatter
     , liftFieldFormatter1
     , formatTextAsIs
     , formatWithShow
     , formatWithShowCSV
-    , writeLines
-    , writeLines'
-    , word8ToTextLines
+    -- * TH Support
+    , sTokenized
+    , ColTypeInfo(..)
+    , readColHeaders
     -- * debugging
-    , streamWord8
-    , streamTextLines
-    , streamTokenized
-    , streamParsed
-    , streamParsedMaybe
+      -- * Re-exports
+    , Separator(..)
+    , QuoteChar
+    , QuotingMode(..)
+    , defaultSep
+    , defaultQuotingMode
+    , FramesCSVException(..)
+    , ParseColumnSelector
+    , ColumnIdType
+      -- * Parsing scan
+    , Acc(..)
+    , accToMaybe
+    , parsingScanF
+    , parseOne
     )
 where
 
+import qualified Frames.Streamly.Internal.CSV as ICSV
+import Frames.Streamly.Internal.CSV (FramesCSVException(..), ParseColumnSelector, ColumnIdType)
+import Frames.Streamly.Streaming.Class (StreamFunctions(..), StreamFunctionsIO(..))
+import Frames.Streamly.Streaming.Common (Separator(..)
+                                        , QuoteChar
+                                        , QuotingMode(..)
+                                        , defaultSep
+                                        , defaultQuotingMode
+                                        , textToSeparator
+                                        , separatorToText)
+--import qualified Frames.Streamly.Streaming.Common as Streaming
+import qualified Frames.Streamly.ColumnTypeable as FSCT
+
 import Prelude hiding(getCompose)
-import qualified Streamly.Prelude                       as Streamly
 
---import qualified Streamly                               as Streamly
---import           Streamly                                ( IsStream )
-import qualified Streamly.Data.Fold                     as Streamly.Fold
-#if MIN_VERSION_streamly(0,8,0)
-import Streamly.Prelude                       (IsStream)
-import qualified Streamly.Internal.Unicode.Array.Char as Streamly.Unicode.Array
-import qualified Streamly.Data.Array.Foreign as Streamly.Array
-import qualified Streamly.Unicode.Stream           as Streamly.Unicode
-#else
-import qualified Streamly                               as Streamly
-import           Streamly                                ( IsStream )
-import qualified Streamly.Internal.Memory.Unicode.Array as Streamly.Unicode.Array
-import qualified Streamly.Internal.Memory.Array.Types as Streamly.Array
-import qualified Streamly.Data.Unicode.Stream           as Streamly.Unicode
-#endif
+import           Control.Monad.Catch                     ( MonadThrow(..))
 
-import qualified Streamly.Internal.FileSystem.File      as Streamly.File
-import qualified Streamly.Internal.Data.Unfold          as Streamly.Unfold
-import           Control.Monad.Catch                     ( MonadCatch )
+import Language.Haskell.TH.Syntax (Lift)
+import qualified Data.Set as Set
 
 import qualified Data.Strict.Either as Strict
+import qualified Data.Strict.Maybe as Strict
 import qualified Data.Text                              as T
 
 import qualified Data.Vinyl                             as Vinyl
@@ -114,45 +136,68 @@
 import qualified Data.Vinyl.TypeLevel                   as Vinyl
 import qualified Data.Vinyl.Class.Method                as Vinyl
 
-import qualified Frames                                 as Frames
-import qualified Frames.CSV                             as Frames
+import qualified Frames
 import qualified Frames.ShowCSV                         as Frames
-import qualified Frames.ColumnTypeable                  as Frames
 import qualified Data.Vinyl as V
 import qualified Data.Vinyl.Functor as V (Compose(..), (:.))
-import GHC.TypeLits (KnownSymbol)
+import GHC.TypeLits (KnownSymbol, ErrorMessage (Text))
 
+-- | Holds column separator, quote handling and column selection information.
+-- NB: This is generated by the 'tableTypes` family of functions
+-- in Frames.Streamly.TH. That value should be used if at all possible
+-- since it carries the correct column selection info.
+data ParserOptions = ParserOptions
+  {
+    columnSelector :: ICSV.ParseColumnSelector
+  , columnSeparator :: Separator
+  , quotingMode :: QuotingMode
+  } deriving Lift
 
+{-
+-- | Pull out the parts of 'ParserOptions' required for tokenizing and
+-- paste into Frames ParserOptions to pass to Frames tokenizer.
+framesParserOptionsForTokenizing :: ParserOptions -> Frames.ParserOptions
+framesParserOptionsForTokenizing (ParserOptions _ cs qm) = Frames.ParserOptions Nothing (Streaming.separatorToText cs) qm
+{-# INLINE framesParserOptionsForTokenizing #-}
+-}
 
+-- | A sensible default *only* for situations when all columns should be parsed.
+defaultParser :: ParserOptions
+defaultParser = ParserOptions (ICSV.ParseAll True) (CharSeparator ',') (RFC4180Quoting '\"')
+{-# INLINEABLE defaultParser #-}
+
+-- | Write a stream of Text to file at FilePath
+writeLines :: StreamFunctionsIO s m => FilePath -> s (IOSafe s m) Text -> m ()
+writeLines = sWriteTextLines
+{-# INLINEABLE writeLines #-}
+
 -- | Given a stream of @Records@, for which all fields satisfy the `ShowCSV` constraint,
 -- produce a stream of `Text`, one item (line) per `Record` with the specified separator
 -- between fields.
 streamToSV
-  :: forall rs m t.
+  :: forall rs m s.
      ( Frames.ColumnHeaders rs
-     , MonadIO m
      , Vinyl.RecordToList rs
      , Vinyl.RecMapMethod Frames.ShowCSV Vinyl.ElField rs
-     , IsStream t
+     , StreamFunctions s m
      )
   => T.Text -- ^ column separator
-  -> t m (Frames.Record rs) -- ^ stream of Records
-  -> t m T.Text -- ^ stream of 'Text' rows
+  -> s m (Frames.Record rs) -- ^ stream of Records
+  -> s m T.Text -- ^ stream of 'Text' rows
 streamToSV = streamSVClass @Frames.ShowCSV Frames.showCSV
 {-# INLINEABLE streamToSV #-}
 
 -- | Given a stream of @Records@, for which all fields satisfy the `ShowCSV` constraint,
 -- produce a stream of CSV `Text`, one item (line) per `Record`.
 streamToCSV
-  :: forall rs m t
+  :: forall rs m s
      . ( Frames.ColumnHeaders rs
-       , MonadIO m
        , Vinyl.RecordToList rs
        , Vinyl.RecMapMethod Frames.ShowCSV Vinyl.ElField rs
-       , IsStream t
+       , StreamFunctions s m
        )
-  => t m (Frames.Record rs) -- ^ stream of Records
-  -> t m T.Text -- ^ stream of 'Text' rows
+  => s m (Frames.Record rs) -- ^ stream of Records
+  -> s m T.Text -- ^ stream of 'Text' rows
 streamToCSV = streamToSV ","
 {-# INLINEABLE streamToCSV #-}
 
@@ -160,85 +205,112 @@
 -- produce a stream of `Text`, one item (line) per `Record` with the specified separator
 -- between fields.
 streamSV
-  :: forall f rs m t.
+  :: forall f rs m s.
      ( Frames.ColumnHeaders rs
      , Foldable f
-     , MonadIO m
      , Vinyl.RecordToList rs
      , Vinyl.RecMapMethod Frames.ShowCSV Vinyl.ElField rs
-     , IsStream t
+     , StreamFunctions s m
      )
   => T.Text -- ^ column separator
   -> f (Frames.Record rs) -- ^ foldable of Records
-  -> t m T.Text -- ^ stream of 'Text' rows
-streamSV sep = streamToSV sep . Streamly.fromFoldable
+  -> s m T.Text -- ^ stream of 'Text' rows
+streamSV sep = streamToSV sep . sFromFoldable
 {-# INLINEABLE streamSV #-}
 
 -- | Given a foldable of @Records@, for which all fields satisfy the `ShowCSV` constraint,
 -- produce a stream of CSV `Text`, one item (line) per `Record`.
 streamCSV
-  :: forall f rs m t.
+  :: forall f rs m s.
      ( Frames.ColumnHeaders rs
      , Foldable f
-     , MonadIO m
      , Vinyl.RecordToList rs
      , Vinyl.RecMapMethod Frames.ShowCSV Vinyl.ElField rs
-     , IsStream t
+     , StreamFunctions s m
      )
   => f (Frames.Record rs)  -- ^ 'Foldable' of Records
-  -> t m T.Text -- ^ stream of 'Text' rows
+  -> s m T.Text -- ^ stream of 'Text' rows
 streamCSV = streamSV ","
+{-# INLINEABLE streamCSV #-}
 
 -- | Convert @Rec@s to lines of `Text` using a class (which must have an instance
 -- for each type in the record) to covert each field to `Text`.
 streamSVClass
-  :: forall c rs t m .
+  :: forall c rs s m .
       ( Vinyl.RecMapMethod c Vinyl.ElField rs
       , Vinyl.RecordToList rs
       , Frames.ColumnHeaders rs
-      , IsStream t
-      , MonadIO m
+      , StreamFunctions s m
      )
   => (forall a. c a => a -> T.Text) -- ^ @show@-like function for some constraint satisfied by all fields.
   -> T.Text -- ^ column separator
-  -> t m (Frames.Record rs)  -- ^ stream of Records
-  -> t m T.Text -- ^ stream of 'Text' rows
+  -> s m (Frames.Record rs)  -- ^ stream of Records
+  -> s m T.Text -- ^ stream of 'Text' rows
 streamSVClass toTxt sep s =
   (T.intercalate sep . fmap T.pack $ Frames.columnHeaders (Proxy :: Proxy (Frames.Record rs)))
-  `Streamly.cons`
-  (Streamly.map (T.intercalate sep . Vinyl.recordToList . Vinyl.rmapMethod @c aux) s)
-  where
-    aux :: (c (Vinyl.PayloadType Vinyl.ElField a))
-        => Vinyl.ElField a
-        -> Vinyl.Const T.Text a
-    aux (Vinyl.Field x) = Vinyl.Const $ toTxt x
+  `sCons`
+  (sMap (svLineClass @c toTxt sep) s)
+{-# INLINEABLE streamSVClass #-}
 
+svLineClass :: forall c rs .
+               ( Vinyl.RecMapMethod c Vinyl.ElField rs
+               , Vinyl.RecordToList rs
+               , Frames.ColumnHeaders rs
+               )
+  => (forall a. c a => a -> T.Text) -- ^ @show@-like function for some constraint satisfied by all fields.
+  -> T.Text -- ^ column separator
+  -> Frames.Record rs
+  -> T.Text -- ^ stream of 'Text' rows
+svLineClass toTxt sep = T.intercalate sep . Vinyl.recordToList . Vinyl.rmapMethod @c aux where
+  aux :: (c (Vinyl.PayloadType Vinyl.ElField a))
+      => Vinyl.ElField a
+      -> Vinyl.Const T.Text a
+  aux (Vinyl.Field x) = Vinyl.Const $ toTxt x
+{-# INLINEABLE svLineClass #-}
 
+
 -- | Given a record of functions to map each field to Text,
 -- transform a stream of records into a stream of lines of Text,
 -- headers first, with headers/fields separated by the given separator.
 streamSV'
-  :: forall rs t m f.
+  :: forall rs s m f.
      (Vinyl.RecordToList rs
      , Vinyl.RApply rs
      , Frames.ColumnHeaders rs
-     , IsStream t
-     , Streamly.MonadAsync m
+     , StreamFunctions s m
      )
   => Vinyl.Rec (Vinyl.Lift (->) f (Vinyl.Const T.Text)) rs -- ^ Vinyl record of formatting functions for the row-type.
   -> T.Text  -- ^ column separator
-  -> t m (Frames.Rec f rs)  -- ^ stream of Records
-  -> t m T.Text -- ^ stream of 'Text' rows
+  -> s m (Frames.Rec f rs)  -- ^ stream of Records
+  -> s m T.Text -- ^ stream of 'Text' rows
 streamSV' toTextRec sep s =
-  (T.intercalate sep . fmap T.pack $ Frames.columnHeaders (Proxy :: Proxy (Frames.Record rs)))
-  `Streamly.cons`
-  (Streamly.map (T.intercalate sep . Vinyl.recordToList . Vinyl.rapply toTextRec) s)
+ ( T.intercalate sep . fmap T.pack $ Frames.columnHeaders (Proxy :: Proxy (Frames.Record rs)))
+  `sCons`
+  (sMap (svLine toTextRec sep) s)
 {-# INLINEABLE streamSV' #-}
 
+
+svLine :: forall rs f.
+          (Vinyl.RecordToList rs
+          , Vinyl.RApply rs
+          , Frames.ColumnHeaders rs
+          )
+       => Vinyl.Rec (Vinyl.Lift (->) f (Vinyl.Const T.Text)) rs -- ^ Vinyl record of formatting functions for the row-type.
+       -> T.Text-- column separator
+       -> Frames.Rec f rs
+       -> T.Text
+svLine toTextRec sep = T.intercalate sep . Vinyl.recordToList . Vinyl.rapply toTextRec
+{-# INLINEABLE svLine #-}
+
 -- | Convert a streamly stream into a (lazy) list
-streamToList :: (IsStream t, Monad m) => t m a -> m [a]
-streamToList = Streamly.toList . Streamly.adapt
+streamToList :: StreamFunctions s m => s m a -> m [a]
+streamToList = sToList
+{-# INLINE streamToList #-}
 
+foldableToStream :: (Foldable f, StreamFunctions s m) => f a -> s m a
+foldableToStream = sFromFoldable
+{-# INLINE foldableToStream #-}
+
 -- | lift a field formatting function into the right form to append to a Rec of formatters
 liftFieldFormatter :: Vinyl.KnownField t
                    => (Vinyl.Snd t -> T.Text) -- ^ formatting function for the type in Field @t@
@@ -255,149 +327,122 @@
 {-# INLINEABLE liftFieldFormatter1 #-}
 
 -- | Format a @Text@ field as-is.
-formatTextAsIs :: (Vinyl.KnownField t, Vinyl.Snd t ~ T.Text) => Vinyl.Lift (->) Vinyl.ElField (Vinyl.Const T.Text) t
+formatTextAsIs :: (Vinyl.KnownField t, Vinyl.Snd t ~ T.Text)
+               => Vinyl.Lift (->) Vinyl.ElField (Vinyl.Const T.Text) t
 formatTextAsIs = liftFieldFormatter id
 {-# INLINE formatTextAsIs #-}
 
 -- | Format a field using the @Show@ instance of the contained type
-formatWithShow :: (Vinyl.KnownField t, Show (Vinyl.Snd t)) => Vinyl.Lift (->) Vinyl.ElField (Vinyl.Const T.Text) t
+formatWithShow :: (Vinyl.KnownField t, Show (Vinyl.Snd t))
+               => Vinyl.Lift (->) Vinyl.ElField (Vinyl.Const T.Text) t
 formatWithShow = liftFieldFormatter $ T.pack . show
 {-# INLINE formatWithShow #-}
 
 -- | Format a field using the @Frames.ShowCSV@ instance of the contained type
-formatWithShowCSV :: (Vinyl.KnownField t, Frames.ShowCSV (Vinyl.Snd t)) => Vinyl.Lift (->) Vinyl.ElField (Vinyl.Const T.Text) t
+formatWithShowCSV :: (Vinyl.KnownField t, Frames.ShowCSV (Vinyl.Snd t))
+                  => Vinyl.Lift (->) Vinyl.ElField (Vinyl.Const T.Text) t
 formatWithShowCSV = liftFieldFormatter Frames.showCSV
 {-# INLINE formatWithShowCSV #-}
 
 -- NB: Uses some internal modules from Streamly.  Will have to change when they become stable
--- | write a stream of @Text@ to a file, one line per stream item.
-writeLines' :: (Streamly.MonadAsync m, MonadCatch m, Streamly.IsStream t) => FilePath -> t m T.Text -> m ()
-writeLines' fp s = do
-#if MIN_VERSION_streamly(0,8,0)
-  let unfoldMany = Streamly.unfoldMany
-#else
-  let unfoldMany = Streamly.concatUnfold
-#endif
-  Streamly.fold (Streamly.File.write fp)
-    $ Streamly.Unicode.encodeUtf8
-    $ Streamly.adapt
-    $ unfoldMany Streamly.Unfold.fromList
-    $ Streamly.map T.unpack
-    $ Streamly.intersperse "\n" s
-{-# INLINEABLE writeLines' #-}
-
--- | write a stream of @Text@ to a file, one line per stream item.
--- | Monomorphised to serial streams for ease of use.
-writeLines :: (Streamly.MonadAsync m, MonadCatch m) => FilePath -> Streamly.SerialT m T.Text -> m ()
-writeLines = writeLines'
-{-# INLINE writeLines #-}
-
--- NB: Uses some internal modules from Streamly.  Will have to change when they become stable
 -- | write a stream of @Records@ to a file, one line per @Record@.
 -- Use the 'Frames.ShowCSV' class to format each field to @Text@
 writeStreamSV
-  ::  forall rs m t.
+  ::  forall rs m s.
    ( Frames.ColumnHeaders rs
-   , MonadCatch m
    , Vinyl.RecordToList rs
    , Vinyl.RecMapMethod Frames.ShowCSV Vinyl.ElField rs
-   , IsStream t
-   , Streamly.MonadAsync m
+   , StreamFunctionsIO s m
    )
   => T.Text -- ^ column separator
   -> FilePath -- ^ path
-  -> t m (Frames.Record rs) -- ^ stream of Records
+  -> s (IOSafe s m) (Frames.Record rs) -- ^ stream of Records
   -> m ()
-writeStreamSV sep fp = writeLines' fp . streamToSV sep
+writeStreamSV sep fp = sWriteTextLines fp . streamToSV sep
 {-# INLINEABLE writeStreamSV #-}
 
 -- | write a foldable of @Records@ to a file, one line per @Record@.
 -- Use the 'Frames.ShowCSV' class to format each field to @Text@
 writeSV
-  ::  forall rs m f.
+  ::  forall rs m f s.
    ( Frames.ColumnHeaders rs
-   , MonadCatch m
    , Vinyl.RecordToList rs
    , Vinyl.RecMapMethod Frames.ShowCSV Vinyl.ElField rs
-   , Streamly.MonadAsync m
+   , StreamFunctionsIO s m
    , Foldable f
    )
   => T.Text -- ^ column separator
   -> FilePath -- ^ file path
   -> f (Frames.Record rs) -- ^ Foldable of Records
   -> m ()
-writeSV sep fp = writeStreamSV sep fp . Streamly.fromFoldable @Streamly.AheadT
+writeSV sep fp = writeStreamSV @_ @_ @s sep fp . sFromFoldable
 {-# INLINEABLE writeSV #-}
 
 -- | write a foldable of @Records@ to a file, one line per @Record@.
 -- Use the 'Frames.ShowCSV' class to format each field to @Text@
 writeCSV
-  ::  forall rs m f.
+  ::  forall rs m f s.
    ( Frames.ColumnHeaders rs
-   , MonadCatch m
    , Vinyl.RecordToList rs
    , Vinyl.RecMapMethod Frames.ShowCSV Vinyl.ElField rs
-   , Streamly.MonadAsync m
+   , StreamFunctionsIO s m
    , Foldable f
    )
   => FilePath -- ^ file path
   -> f (Frames.Record rs) -- ^ 'Foldable' of Records
   -> m ()
-writeCSV fp = writeSV "," fp
+writeCSV = writeSV @_ @_ @_ @s ","
 {-# INLINEABLE writeCSV #-}
 
 -- NB: Uses some internal modules from Streamly.  Will have to change when they become stable
+
 -- | write a stream of @Records@ to a file, one line per @Record@.
 -- Use the 'Show' class to format each field to @Text@
 writeStreamSV_Show
-  ::  forall rs m t.
+  ::  forall rs m s.
    ( Frames.ColumnHeaders rs
-   , MonadCatch m
    , Vinyl.RecordToList rs
    , Vinyl.RecMapMethod Show Vinyl.ElField rs
-   , IsStream t
-   , Streamly.MonadAsync m
+   , StreamFunctionsIO s m
    )
   => T.Text -- ^ column separator
   -> FilePath -- ^ file path
-  -> t m (Frames.Record rs) -- ^ stream of Records
+  -> s (IOSafe s m) (Frames.Record rs) -- ^ stream of Records
   -> m ()
-writeStreamSV_Show sep fp = writeLines' fp . streamSVClass @Show (T.pack . show) sep
+writeStreamSV_Show sep fp = sWriteTextLines fp . streamSVClass @Show (T.pack . show) sep
 {-# INLINEABLE writeStreamSV_Show #-}
 
 -- | write a foldable of @Records@ to a file, one line per @Record@.
 -- Use the 'Show' class to format each field to @Text@
 writeSV_Show
-  ::  forall rs m f.
+  ::  forall rs m f s.
    ( Frames.ColumnHeaders rs
-   , MonadCatch m
    , Vinyl.RecordToList rs
    , Vinyl.RecMapMethod Show Vinyl.ElField rs
-   , Streamly.MonadAsync m
+   , StreamFunctionsIO s m
    , Foldable f
    )
   => T.Text -- ^ column separator
   -> FilePath  -- ^ file path
   -> f (Frames.Record rs) -- ^ 'Foldable' of Records
   -> m ()
-writeSV_Show sep fp = writeStreamSV_Show sep fp . Streamly.fromFoldable @Streamly.AheadT
+writeSV_Show sep fp = writeStreamSV_Show @_ @_ @s sep fp . sFromFoldable
 {-# INLINEABLE writeSV_Show #-}
 
 -- | write a foldable of @Records@ to a file, one line per @Record@.
 -- Use the 'Show' class to format each field to @Text@
 writeCSV_Show
-  ::  forall rs m f.
+  ::  forall rs m f s.
    ( Frames.ColumnHeaders rs
-   , MonadCatch m
    , Vinyl.RecordToList rs
    , Vinyl.RecMapMethod Show Vinyl.ElField rs
-   , Streamly.MonadAsync m
+   , StreamFunctionsIO s m
    , Foldable f
    )
   => FilePath -- ^ file path
   -> f (Frames.Record rs) -- ^ 'Foldable' of Records
   -> m ()
-writeCSV_Show fp = writeSV_Show "," fp
+writeCSV_Show = writeSV_Show @_ @_ @_ @s ","
 {-# INLINEABLE writeCSV_Show #-}
 
 -- Thanks to Tim Pierson for the functions below!
@@ -406,31 +451,31 @@
 -- Results composed with the @Maybe@ functor. Unparsed fields are returned as @Nothing@.
 -- NB:  If the inferred/given rs is different from the actual file row-type, things will go awry.
 readTableMaybe
-    :: forall rs t m.
-    (Streamly.MonadAsync m
-    , MonadCatch m
-    , IsStream t
-    , Vinyl.RMap rs
-    , Frames.ReadRec rs)
+    :: forall rs s m.
+    ( Vinyl.RMap rs
+    , StrictReadRec rs
+    , MonadThrow m
+    , StreamFunctionsIO s m
+    )
     => FilePath -- ^ file path
-    -> t m (Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs) -- ^ stream of @Maybe :. ElField@ records after parsing.
-readTableMaybe = readTableMaybeOpt Frames.defaultParser
+    -> s (IOSafe s m) (Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs) -- ^ stream of @Maybe :. ElField@ records after parsing.
+readTableMaybe = readTableMaybeOpt @_ @s @m defaultParser
 {-# INLINEABLE readTableMaybe #-}
 
 -- | Stream a table from a file path.
 -- Results composed with the @Maybe@ functor. Unparsed fields are returned as @Nothing@.
 -- NB:  If the inferred/given rs is different from the actual file row-type, things will go awry.
 readTableMaybeOpt
-    :: forall rs t m.
-    (Streamly.MonadAsync m
-    , MonadCatch m
-    , IsStream t
-    , Vinyl.RMap rs
-    , Frames.ReadRec rs)
-    => Frames.ParserOptions -- ^ parsing options
+    :: forall rs s m.
+    ( Vinyl.RMap rs
+    , StrictReadRec rs
+    , MonadThrow m
+    , StreamFunctionsIO s m
+    )
+    => ParserOptions -- ^ parsing options
     -> FilePath -- ^ file path
-    -> t m (Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs) -- ^ stream of @Maybe :. ElField@ records after parsing.
-readTableMaybeOpt opts = Streamly.map recEitherToMaybe . readTableEitherOpt opts
+    -> s (IOSafe s m) (Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs) -- ^ stream of @Maybe :. ElField@ records after parsing.
+readTableMaybeOpt opts = sMap recEitherToMaybe . readTableEitherOpt @_ @s @m opts
 {-# INLINEABLE readTableMaybeOpt #-}
 
 -- | Stream a table from a file path.
@@ -439,62 +484,59 @@
 -- Uses default options.
 -- NB:  If the inferred/given rs is different from the actual file row-type, things will go awry.
 readTableEither
-  :: forall rs t m.
-     (Streamly.MonadAsync m
-     , MonadCatch m
-     , IsStream t
-     , Vinyl.RMap rs
-     , Frames.ReadRec rs)
+  :: forall rs s m.
+     ( Vinyl.RMap rs
+     , StrictReadRec rs
+     , MonadThrow m
+     , StreamFunctionsIO s m
+     )
   => FilePath -- ^ file path
-  -> t m (Vinyl.Rec (Either T.Text Vinyl.:. Vinyl.ElField) rs) -- ^ stream of @Either :. ElField@ records after parsing.
-readTableEither = readTableEitherOpt Frames.defaultParser
+  -> s (IOSafe s m) (Vinyl.Rec (Either T.Text Vinyl.:. Vinyl.ElField) rs) -- ^ stream of @Either :. ElField@ records after parsing.
+readTableEither = readTableEitherOpt @rs @s @m defaultParser
+{-# INLINEABLE readTableEither #-}
 
 -- | Stream a table from a file path.
 -- Results composed with the @Either Text@ functor. Unparsed fields are returned as a @Left@
 -- containing the string that failed to parse.
 -- NB:  If the inferred/given rs is different from the actual file row-type, things will go awry.
 readTableEitherOpt
-  :: forall rs t m.
-     (Streamly.MonadAsync m
-     , MonadCatch m
-     , IsStream t
-     , Vinyl.RMap rs
-     , Frames.ReadRec rs)
-  => Frames.ParserOptions -- ^ parsing options
+  :: forall rs s m.
+     ( Vinyl.RMap rs
+     , StrictReadRec rs
+     , StreamFunctionsIO s m
+     )
+  => ParserOptions -- ^ parsing options
   -> FilePath -- ^ file path
-  -> t m (Vinyl.Rec (Either T.Text Vinyl.:. Vinyl.ElField) rs) -- ^ stream of @Either :. ElField@ records after parsing.
-readTableEitherOpt opts = streamTableEitherOpt opts . word8ToTextLines . Streamly.File.toBytes
+  -> s (IOSafe s m) (Vinyl.Rec (Either T.Text Vinyl.:. Vinyl.ElField) rs) -- ^ stream of @Either :. ElField@ records after parsing.
+readTableEitherOpt opts = streamTableEitherOpt @rs @s @(IOSafe s m) opts . sTokenized @s @m (columnSeparator opts) (quotingMode opts)
 {-# INLINEABLE readTableEitherOpt #-}
 
-
 -- | Stream Table from a file path, dropping rows where any field fails to parse
 -- | Use default options
 -- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will go awry.
 readTable
-  :: forall rs t m.
-     (Streamly.MonadAsync m
-     , MonadCatch m
-     , IsStream t
-     , Vinyl.RMap rs
-     , StrictReadRec rs)
+  :: forall rs s m.
+     ( Vinyl.RMap rs
+     , StrictReadRec rs
+     , StreamFunctionsIO s m
+     )
   => FilePath -- ^ file path
-  -> t m (Frames.Record rs) -- ^ stream of Records
-readTable = readTableOpt Frames.defaultParser
+  -> s (IOSafe s m) (Frames.Record rs) -- ^ stream of Records
+readTable = readTableOpt @rs @s @m defaultParser
 {-# INLINEABLE readTable #-}
 
 -- | Stream Table from a file path, dropping rows where any field fails to parse
 -- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will go awry.
 readTableOpt
-  :: forall rs t m.
-     (Streamly.MonadAsync m
-     , MonadCatch m
-     , IsStream t
-     , Vinyl.RMap rs
-     , StrictReadRec rs)
-  => Frames.ParserOptions  -- ^ parsing options
+  :: forall rs s m.
+     ( Vinyl.RMap rs
+     , StrictReadRec rs
+     , StreamFunctionsIO s m
+     )
+  => ParserOptions  -- ^ parsing options
   -> FilePath -- ^ file path
-  -> t m (Frames.Record rs)  -- ^ stream of Records
-readTableOpt !opts !fp = streamTableOpt opts $! word8ToTextLines $! Streamly.File.toBytes fp
+  -> s (IOSafe s m) (Frames.Record rs)  -- ^ stream of Records
+readTableOpt !opts !fp = streamTableOpt @rs @s @(IOSafe s m) opts $! sTokenized @s @m (columnSeparator opts) (quotingMode opts) fp
 {-# INLINEABLE readTableOpt #-}
 
 -- | Convert a stream of lines of `Text` to a table
@@ -503,66 +545,138 @@
 --
 -- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will go awry.
 streamTableEither
-    :: forall rs t m.
-    (Streamly.MonadAsync m
-    , IsStream t
-    , Vinyl.RMap rs
-    , Frames.ReadRec rs)
-    => t m T.Text -- ^ stream of 'Text' rows
-    -> t m (Vinyl.Rec ((Either T.Text) Vinyl.:. Vinyl.ElField) rs) -- ^ stream of parsed @Either :. ElField@ rows
-streamTableEither = streamTableEitherOpt Frames.defaultParser
+    :: forall rs s m.
+    ( Vinyl.RMap rs
+    , StrictReadRec rs
+    , MonadThrow m
+    , StreamFunctions s m
+    )
+    => s m [T.Text] -- ^ stream of 'Text' rows
+    -> s m (Vinyl.Rec (Either T.Text Vinyl.:. Vinyl.ElField) rs) -- ^ stream of parsed @Either :. ElField@ rows
+streamTableEither = streamTableEitherOpt defaultParser
 {-# INLINEABLE streamTableEither #-}
 
+
+data Acc b = AccInitial
+           | AccResult  !(Maybe [Bool]) !(Strict.Maybe b)
+
+accToMaybe :: Acc b -> Maybe b
+accToMaybe AccInitial = Nothing
+accToMaybe (AccResult _ smb) = case smb of
+  Strict.Nothing -> Nothing
+  Strict.Just x -> Just x
+{-# INLINEABLE accToMaybe #-}
+
+-- | Various parsing options have to handle the header line, if it exists,
+-- differently.  This function pulls all that logic into one place.
+-- We take the 'ParserOptions' and the stream of un-tokenized 'Text' lines
+-- and do whatever is required, checking for various errors
+-- (empty stream, missing headers, wrong number of columns when using positions for typing/naming)
+-- along the way.
+handleHeader :: forall m. (MonadThrow m)
+             => ParserOptions
+             -> [T.Text]
+             -> m (Maybe [Bool], Bool)
+handleHeader opts t = case columnSelector opts of
+  ICSV.ParseAll True -> return (Nothing, True)
+  ICSV.ParseAll False ->  return (Nothing, False)
+  ICSV.ParseIgnoringHeader cs -> checkNumFirstRowCols t cs >> return (Just $ csToBool <$> cs, True)
+  ICSV.ParseWithoutHeader cs -> checkNumFirstRowCols t cs >> return (Just $ csToBool <$> cs, False)
+  ICSV.ParseUsingHeader hs -> (, True) . Just <$> boolsFromHeader hs t
+  where
+    csToBool x = x /= ICSV.Exclude
+
+--    tokenizedFirstRow :: T.Text -> [Text]
+--    tokenizedFirstRow x = Frames.tokenizeRow (framesParserOptionsForTokenizing opts) x
+
+    boolsFromHeader :: [ICSV.HeaderText] ->  [T.Text] -> m [Bool]
+    boolsFromHeader hs x = do
+      let headersToInclude = Set.fromList hs
+          includeHeader y = y `Set.member` headersToInclude
+          fileHeaders = ICSV.HeaderText <$> x
+          fileHeadersS = Set.fromList fileHeaders
+          notPresentM y = if y `Set.member` fileHeadersS then Nothing else Just y
+          missingIncluded = mapMaybe notPresentM hs
+      unless (null missingIncluded) $ throwM $ ICSV.MissingHeadersException missingIncluded
+      let bools = includeHeader <$> fileHeaders
+      return bools
+
+    checkNumFirstRowCols :: [T.Text] -> [ICSV.ColumnState] -> m ()
+    checkNumFirstRowCols x cs = checkSameLength cs x
+
+    checkSameLength :: [ICSV.ColumnState] -> [b] -> m ()
+    checkSameLength givenCSs streamCols = do
+      let nGiven = length givenCSs
+          nStreamCols = length streamCols
+          errMsg = "Number of given columns from type generation (" <> show nGiven
+                   <> ") doesn't match the number of columns in the parsed file ("
+                   <> show nStreamCols <> ")"
+      when (nGiven /= nStreamCols) $ throwM $ ICSV.WrongNumberColumnsException errMsg
+      return ()
+
+parsingScanF :: MonadThrow m
+             => ParserOptions
+             -> (Maybe [Bool] -> [Text] -> Strict.Maybe b)
+             -> (Acc b -> [Text] -> m (Acc b))
+parsingScanF opts pF sta t = case sta of
+  AccInitial -> do
+    (rF, dropFirst) <- handleHeader opts t
+    let res = if dropFirst then Strict.Nothing else pF rF t
+    return $ AccResult rF res
+  AccResult rF _ -> return $ AccResult rF $ pF rF t
+{-# INLINEABLE parsingScanF #-}
+
 -- | Convert a stream of lines of `Text` to records.
 -- Each field is returned in an @Either Text@ functor. @Right a@ for successful parses
 -- and @Left Text@ when parsing fails, containing the text that failed to Parse.
 --
--- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will..go awry.
+-- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will...go awry.
 streamTableEitherOpt
-    :: forall rs t m.
-    (Streamly.MonadAsync m
-    , IsStream t
-    , Vinyl.RMap rs
-    , Frames.ReadRec rs)
-    => Frames.ParserOptions -- ^ parsing options
-    -> t m T.Text -- ^ stream of 'Text' rows
-    -> t m (Vinyl.Rec ((Either T.Text) Vinyl.:. Vinyl.ElField) rs)  -- ^ stream of parsed @Either :. ElField@ rows
-streamTableEitherOpt opts =
-    Streamly.map (parse . Frames.tokenizeRow opts)
-    . handleHeader
+    :: forall rs s m.
+    ( Vinyl.RMap rs
+    , StrictReadRec rs
+    , MonadThrow m
+    , StreamFunctions s m
+    )
+    => ParserOptions -- ^ parsing options
+    -> s m [T.Text] -- ^ stream of 'Text' rows
+    -> s m (Vinyl.Rec ((Either T.Text) Vinyl.:. Vinyl.ElField) rs)  -- ^ stream of parsed @Either :. ElField@ rows
+streamTableEitherOpt opts = sMapMaybe accToMaybe . sScanM (parsingScanF opts parseOne') (return AccInitial)
   where
-    handleHeader | isNothing (Frames.headerOverride opts) = Streamly.drop 1
-                 | otherwise                       = id
-    parse = Frames.readRec
+    parseOne' :: Maybe [Bool] -> [Text] -> Strict.Maybe (Vinyl.Rec ((Either T.Text) Vinyl.:. Vinyl.ElField) rs)
+    parseOne' rF = Strict.Just . recUnStrictEither . parse . useRowFilter rF
+    parse = strictReadRec
 {-# INLINEABLE streamTableEitherOpt #-}
 
 -- | Convert a stream of lines of `Text` to a table.
 --
 -- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will..go awry.
 streamTableMaybe
-    :: forall rs t m.
-    (Streamly.MonadAsync m
-    , IsStream t
-    , Vinyl.RMap rs
-    , Frames.ReadRec rs)
-    => t m T.Text -- ^ stream of 'Text' rows
-    -> t m (Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs) -- ^ stream of parsed @Maybe :. ElField@ rows
-streamTableMaybe = streamTableMaybeOpt Frames.defaultParser
+    :: forall rs s m.
+    ( Vinyl.RMap rs
+    , StrictReadRec rs
+    , MonadThrow m
+    , StreamFunctions s m
+    )
+    => s m [T.Text] -- ^ stream of 'Text' rows
+    -> s m (Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs) -- ^ stream of parsed @Maybe :. ElField@ rows
+streamTableMaybe = streamTableMaybeOpt defaultParser
 {-# INLINEABLE streamTableMaybe #-}
 
 -- | Convert a stream of lines of Text to a table .
 --
 -- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will..go awry.
 streamTableMaybeOpt
-    :: forall rs t m.
-    (Streamly.MonadAsync m
-    , IsStream t
-    , Vinyl.RMap rs
-    , Frames.ReadRec rs)
-    => Frames.ParserOptions -- ^ parsing options
-    -> t m T.Text -- ^ stream of 'Text' rows
-    -> t m (Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs) -- ^ stream of parsed @Maybe :. ElField@ rows
-streamTableMaybeOpt opts = Streamly.map recEitherToMaybe . streamTableEitherOpt opts
+    :: forall rs s m.
+    (Vinyl.RMap rs
+    , StrictReadRec rs
+    , MonadThrow m
+    , StreamFunctions s m
+    )
+    => ParserOptions -- ^ parsing options
+    -> s m [T.Text] -- ^ stream of 'Text' rows
+    -> s m (Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs) -- ^ stream of parsed @Maybe :. ElField@ rows
+streamTableMaybeOpt opts = sMap recEitherToMaybe . streamTableEitherOpt opts
 {-# INLINEABLE streamTableMaybeOpt #-}
 
 -- | Convert a stream of lines of 'Text' to a table,
@@ -570,126 +684,193 @@
 -- Use default options.
 -- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will go awry.
 streamTable
-    :: forall rs t m.
-    (Streamly.MonadAsync m
-    , IsStream t
-    , Vinyl.RMap rs
+    :: forall rs s m.
+    ( Vinyl.RMap rs
     , StrictReadRec rs
+    , MonadThrow m
+    , StreamFunctions s m
     )
-    => t m T.Text -- ^ stream of 'Text' rows
-    -> t m (Frames.Record rs) -- ^ stream of Records
-streamTable = streamTableOpt Frames.defaultParser
+    => s m [T.Text] -- ^ stream of 'Text' rows
+    -> s m (Frames.Record rs) -- ^ stream of Records
+streamTable = streamTableOpt defaultParser
 {-# INLINEABLE streamTable #-}
 
 -- | Convert a stream of lines of 'Text' `Word8` to a table,
 -- dropping rows where any field fails to parse.
 -- NB:  If the inferred/given @rs@ is different from the actual file row-type, things will go awry.
 streamTableOpt
-    :: forall rs t m.
-    (Streamly.MonadAsync m
-    , IsStream t
-    , Vinyl.RMap rs
+    :: forall rs s m.
+    ( Vinyl.RMap rs
     , StrictReadRec rs
+    , MonadThrow m
+    , StreamFunctions s m
     )
-    => Frames.ParserOptions -- ^ parsing options
-    -> t m T.Text  -- ^ stream of 'Text' rows
-    -> t m (Frames.Record rs) -- ^ stream of Records
-streamTableOpt opts = Streamly.mapMaybe (mRec opts) . handleHeader
-  where
-    handleHeader | isNothing (Frames.headerOverride opts) = Streamly.drop 1
-                 | otherwise                       = id
-
---    doParse = recStrictEitherToMaybe . recEitherToStrict . Frames.readRec
---{-# INLINE streamTableOpt #-}
+    => ParserOptions -- ^ parsing options
+    -> s m [T.Text]  -- ^ stream of 'Text' rows
+    -> s m (Frames.Record rs) -- ^ stream of Records
+streamTableOpt opts = sMapMaybe accToMaybe . sScanM (parsingScanF opts parseOne) (return AccInitial)
+{-# INLINEABLE streamTableOpt #-}
 
-doParse :: (V.RMap rs, StrictReadRec rs) => [Text] -> V.Rec (Maybe V.:. V.ElField) rs
-doParse !x = recStrictEitherToMaybe $! strictReadRec x
+parseOne :: (V.RMap rs
+            , StrictReadRec rs
+            )
+         => Maybe [Bool] -> [Text] -> Strict.Maybe (Frames.Record rs)
+parseOne rF t = maybe Strict.Nothing Strict.Just $! Frames.recMaybe $! doParseStrict $! useRowFilter rF t
+{-# INLINEABLE parseOne #-}
 
-mRec :: (V.RMap rs, StrictReadRec rs) => Frames.ParserOptions -> Text -> Maybe (V.Rec V.ElField rs)
-mRec !opts !x = recMaybe $! doParse $! Frames.tokenizeRow opts x
+-- | Parse using StrictReadRec
+doParseStrict :: (V.RMap rs, StrictReadRec rs) => [Text] -> V.Rec (Maybe V.:. V.ElField) rs
+doParseStrict !x = recStrictEitherToMaybe $! strictReadRec x
+{-# INLINEABLE doParseStrict #-}
 
 
 recEitherToMaybe :: Vinyl.RMap rs => Vinyl.Rec (Either T.Text Vinyl.:. Vinyl.ElField) rs -> Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs
 recEitherToMaybe = Vinyl.rmap (either (const (Vinyl.Compose Nothing)) (Vinyl.Compose . Just) . Vinyl.getCompose)
---{-# INLINE recEitherToMaybe #-}
+{-# INLINEABLE recEitherToMaybe #-}
 
 recStrictEitherToMaybe :: Vinyl.RMap rs => Vinyl.Rec (Strict.Either T.Text Vinyl.:. Vinyl.ElField) rs -> Vinyl.Rec (Maybe Vinyl.:. Vinyl.ElField) rs
 recStrictEitherToMaybe = Vinyl.rmap (Strict.either (const (Vinyl.Compose Nothing)) (Vinyl.Compose . Just) . Vinyl.getCompose)
---{-# INLINE recStrictEitherToMaybe #-}
-
-{-
-recEitherToStrict :: Vinyl.RMap rs => Vinyl.Rec (Either T.Text Vinyl.:. Vinyl.ElField) rs -> Vinyl.Rec (Strict.Either T.Text Vinyl.:. Vinyl.ElField) rs
-recEitherToStrict = Vinyl.rmap (Vinyl.Compose . either Strict.Left Strict.Right . Vinyl.getCompose)
---{-# INLINE recEitherToStrict #-}
+{-# INLINEABLE recStrictEitherToMaybe #-}
 
 recUnStrictEither :: Vinyl.RMap rs => Vinyl.Rec (Strict.Either T.Text Vinyl.:. Vinyl.ElField) rs -> Vinyl.Rec (Either T.Text Vinyl.:. Vinyl.ElField) rs
 recUnStrictEither = Vinyl.rmap (Vinyl.Compose . Strict.either Left Right . Vinyl.getCompose)
---{-# INLINE recUnStrictEither #-}
--}
+{-# INLINEABLE recUnStrictEither #-}
+{-
 -- | Convert a stream of Word8 to lines of `Text` by decoding as UTF8 and splitting on "\n"
 word8ToTextLines :: (IsStream t, MonadIO m) => t m Word8 -> t m T.Text
 word8ToTextLines =  Streamly.splitOnSuffix(=='\n') (toText <$> Streamly.Fold.toList)
                     . Streamly.Unicode.decodeUtf8
---{-# INLINE word8ToTextLines #-}
+{-# INLINE word8ToTextLines #-}
 
 word8ToTextLines2 :: (IsStream t, MonadIO m) => t m Word8 -> t m T.Text
 word8ToTextLines2 =  Streamly.map (toText . Streamly.Array.toList)
                      . Streamly.Unicode.Array.lines
                      . Streamly.Unicode.decodeUtf8
---{-# INLINE word8ToTextLines2 #-}
-
+{-# INLINE word8ToTextLines2 #-}
+-}
+-- | Parse list of text into @Rec (Either :. ElField)@ using strict @Either@
+-- Don't make this INLINE!! GHC 8.10.7 goes on forever...
 class StrictReadRec rs where
   strictReadRec :: [Text] -> V.Rec (Strict.Either Text V.:. V.ElField) rs
 
 instance StrictReadRec '[] where
   strictReadRec _ = V.RNil
+  {-# INLINEABLE strictReadRec #-}
+-- Don't make this INLINE!! GHC 8.10.7 goes on forever.   INLINABLE seems okay
 
-instance (Frames.Parseable t, StrictReadRec ts, KnownSymbol s) => StrictReadRec (s Frames.:-> t ': ts) where
+instance (FSCT.Parseable t, StrictReadRec ts, KnownSymbol s) => StrictReadRec (s Frames.:-> t ': ts) where
+  -- This one is neccesary to avoid a warning about incomplete pattern matches. ??
   strictReadRec [] = V.Compose (Strict.Left mempty) V.:& strictReadRec []
+
   strictReadRec (!h : t) = maybe
                            (V.Compose (Strict.Left (T.copy h)))
                            (V.Compose . Strict.Right . V.Field)
-                           (Frames.parse' h)
+                           (FSCT.parse' h)
                            V.:& strictReadRec t
-{-
-strictCons !a !b = a V.:& b
+  {-# INLINEABLE strictReadRec #-}
+-- Don't make this INLINE!! GHC 8.10.7 goes on forever.
 
-strictMaybe :: b -> (a -> b) -> Maybe a -> b
-strictMaybe !b f ma = case ma of
-  Nothing -> b
-  Just !a' -> f a'
+useRowFilter :: Maybe [Bool] -> [Text] -> [Text]
+useRowFilter = maybe id f  where
+  f bs xs = snd <$> filter fst (zip bs xs)
+{-# INLINEABLE useRowFilter #-}
 
+-- for TH inference
+prefixInference :: forall a s m.(MonadThrow m
+                                , Monad m
+                                , StreamFunctions s m
+                                , FSCT.ColumnTypeable a
+                                )
+                => FSCT.Parsers a
+                -> (Text -> Bool)
+                -> Maybe [Bool]
+                -> s m [T.Text]
+                -> m [a]
+prefixInference parsers isMissing rF s = do
+  sThrowIfEmpty s
+  let inferCols = fmap (FSCT.inferType @a parsers isMissing) -- need type application here to know what col-type to infer
+      step ts = zipWith (FSCT.updateWithParse parsers) ts . inferCols
+      start = repeat FSCT.initialColType
 
--- strictParseCons :: (KnownSymbol s, Frames.Parseable t)
---                => Text -> V.Rec (Strict.Either Text V.:. V.ElField) rs -> V.Rec (Strict.Either Text V.:. V.ElField) (s Frames.:-> t ': rs)
-strictParseCons !h !rs = let !parsed = Frames.parse' h in parsed V.:& rs
--}
+  sFolder step start $ sMap (useRowFilter rF) s
+{-# INLINEABLE prefixInference #-}
 
-rtraverse
-  :: Applicative h
-  => (forall x. f x -> h (g x))
-  -> V.Rec f rs
-  -> h (V.Rec g rs)
-rtraverse _ V.RNil      = pure V.RNil
-rtraverse f (x V.:& xs) = (V.:&) <$> (f x)  <*> rtraverse f xs
---{-# INLINABLE rtraverse #-}
+data ColTypeInfo a = ColTypeInfo { colTypeName :: ICSV.ColTypeName, orMissingWhen :: ICSV.OrMissingWhen, colBaseType :: a}
 
-recMaybe :: V.Rec (Maybe V.:. V.ElField) cs -> Maybe (V.Rec V.ElField cs)
-recMaybe  = rtraverse V.getCompose
---{-# INLINEABLE recMaybe #-}
--- tracing fold
+-- | Extract column names and inferred types from a CSV file.
+readColHeaders :: forall a b s m.
+                  (Show (ICSV.ColumnIdType b)
+                  , Monad m
+                  , MonadThrow m
+                  , StreamFunctions s m
+                  , FSCT.ColumnTypeable a
+                  )
+               => FSCT.Parsers a
+               -> ICSV.RowGenColumnSelector b-- headerOverride
+               -> s m [Text]
+               -> m ([ColTypeInfo a], ICSV.ParseColumnSelector)
+readColHeaders parsers rgColHandler s =  do
+  let csToBool =  (/= ICSV.Exclude)
+  (firstRow, mTail) <- sUncons s >>= maybe (throwM ICSV.EmptyStreamException) return
+  -- headerRow :: [(ICSV.ColTypeName , ICSV.OrMissingWhen)]
+  -- pch :: ICSV.ParseColumnSelector
+  -- rF :: Maybe [Bool]
+  (headerRow, pch, rF, inferS) <- case rgColHandler of
+    ICSV.GenUsingHeader f mrF -> do
+      let allHeaders = ICSV.HeaderText <$> firstRow -- (draw >>= maybe err return)
+      checkColumnIds mrF allHeaders
+      let allColStates = f <$> allHeaders
+          allBools = csToBool <$> allColStates
+          includedInfo = ICSV.includedColTypeInfo allColStates
+          parseColHeader = ICSV.colStatesAndHeadersToParseColHandler allColStates allHeaders
+      return (includedInfo, parseColHeader, Just allBools, mTail)
+    ICSV.GenIgnoringHeader f mrF -> do
+      let allHeaders = firstRow -- <- draw >>= maybe err return
+          allIndexes = [0..(length allHeaders - 1)]
+      checkColumnIds mrF allIndexes
+      let allColStates = f  <$> allIndexes
+          allBools = csToBool <$> allColStates
+          includedInfo = ICSV.includedColTypeInfo allColStates
+          parseColHeader = ICSV.ParseIgnoringHeader allColStates
+      return (includedInfo, parseColHeader, Just allBools, mTail)
+    ICSV.GenWithoutHeader f mrF -> do
+      let sampleRow = firstRow -- <- peek >>= maybe err return
+          allIndexes = [0..(length sampleRow - 1)]
+      checkColumnIds mrF allIndexes
+      let allColStates =  f <$> allIndexes
+          allBools = csToBool <$> allColStates
+          includedInfo = ICSV.includedColTypeInfo allColStates
+          parseColHeader = ICSV.ParseWithoutHeader allColStates
+      return (includedInfo, parseColHeader, Just allBools, s)
+  let isMissing t = T.null t || t == "NA"
+      assembleCTI :: (ICSV.ColTypeName, ICSV.OrMissingWhen) -> a -> ColTypeInfo a
+      assembleCTI (a, b) c = ColTypeInfo a b c
+  colTypes <- prefixInference @a @s parsers isMissing rF inferS
+  unless (length headerRow == length colTypes) $ errNumColumns headerRow colTypes
+  return (zipWith assembleCTI headerRow colTypes, pch)
+  where errNumColumns hs cts =
+          throwM
+          $ ICSV.BadHeaderException
+          $ (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."])
+          <> "\nHeaders: " <> show hs <> "\nNumber ColTypes: " <> show (length cts)
+        checkColumnIds :: ICSV.MissingRequiredIdsF b -> [ICSV.ColumnIdType b] -> m ()
+        checkColumnIds mrF fileIds = do
+          let missing = mrF fileIds
+          unless (null missing)
+            $ throwM
+            $ ICSV.BadHeaderException
+            $ "Required columnIds (headers or positions) are missing from file being parsed.  Missing=" <> show missing
+          return ()
+{-# INLINEABLE readColHeaders #-}
 {-
-runningCountF :: MonadIO m => T.Text -> (Int -> T.Text) -> T.Text -> Streamly.Fold.Fold m a ()
-runningCountF startMsg countMsg endMsg = Streamly.Fold.Fold step start done where
-  start = liftIO (T.putStr startMsg) >> return 0
-  step !n _ = liftIO $ do
-    t <- System.Clock.getTime System.Clock.ProcessCPUTime
-    putStr $ show t ++ ": "
-    T.putStrLn $ countMsg n
-    return (n+1)
-  done _ = liftIO $ T.putStrLn endMsg
--}
--- For debugging
 streamWord8 :: (Streamly.IsStream t, Streamly.MonadAsync m, MonadCatch m) => FilePath -> t m Word8
 streamWord8 =  Streamly.File.toBytes
 {-# INLINE streamWord8 #-}
@@ -697,15 +878,25 @@
 streamTextLines :: (Streamly.IsStream t, Streamly.MonadAsync m, MonadCatch m) => FilePath -> t m Text
 streamTextLines = word8ToTextLines2 . streamWord8
 {-# INLINE streamTextLines #-}
+-}
+{-
+streamTokenized' :: forall s m.StreamFunctionsIO s m => FilePath -> Frames.Separator -> s (IOSafe s m) [Text]
+streamTokenized' fp sep =  sMap (fmap T.copy . Frames.tokenizeRow popts) $ sReadTextLines @s @m fp where
+  popts = Frames.defaultParser { Frames.columnSeparator = sep }
+{-# INLINE streamTokenized' #-}
 
-streamTokenized :: (Streamly.IsStream t, Streamly.MonadAsync m, MonadCatch m) => FilePath -> t m [Text]
-streamTokenized =  Streamly.map (fmap T.copy . Frames.tokenizeRow Frames.defaultParser) . streamTextLines
+streamTokenized :: forall s m.StreamFunctionsIO s m => FilePath -> s (IOSafe s m) [Text]
+streamTokenized =  sMap (fmap T.copy . Frames.tokenizeRow Frames.defaultParser) . sReadTextLines @s @m
 {-# INLINE streamTokenized #-}
 
-streamParsed :: (V.RMap rs, StrictReadRec rs) => (Streamly.IsStream t, Streamly.MonadAsync m, MonadCatch m) => FilePath -> t m (V.Rec (Strict.Either Text V.:. V.ElField) rs)
-streamParsed =  Streamly.map (strictReadRec . Frames.tokenizeRow Frames.defaultParser) . streamTextLines
+streamParsed :: forall rs s m.(V.RMap rs, StrictReadRec rs, StreamFunctionsIO s m)
+             => FilePath
+             -> s (IOSafe s m) (V.Rec (Strict.Either Text V.:. V.ElField) rs)
+streamParsed = sMap (strictReadRec . Frames.tokenizeRow Frames.defaultParser) . sReadTextLines @s @m
 {-# INLINE streamParsed #-}
 
-streamParsedMaybe :: (V.RMap rs, StrictReadRec rs) => (Streamly.IsStream t, Streamly.MonadAsync m, MonadCatch m) => FilePath -> t m (V.Rec (Maybe V.:. V.ElField) rs)
-streamParsedMaybe =  Streamly.map (recStrictEitherToMaybe . strictReadRec . Frames.tokenizeRow Frames.defaultParser) . streamTextLines
+streamParsedMaybe :: forall rs s m.(V.RMap rs, StrictReadRec rs, StreamFunctionsIO s m)
+                  => FilePath -> s (IOSafe s m) (V.Rec (Maybe V.:. V.ElField) rs)
+streamParsedMaybe =  sMap (recStrictEitherToMaybe . strictReadRec . Frames.tokenizeRow Frames.defaultParser) . sReadTextLines @s @m
 {-# INLINE streamParsedMaybe #-}
+-}
diff --git a/src/Frames/Streamly/Categorical.hs b/src/Frames/Streamly/Categorical.hs
new file mode 100644
--- /dev/null
+++ b/src/Frames/Streamly/Categorical.hs
@@ -0,0 +1,143 @@
+{-# LANGUAGE CPP, DataKinds, KindSignatures, MagicHash,
+             ScopedTypeVariables, TemplateHaskell, TypeFamilies,
+             ViewPatterns #-}
+-- | Support for representing so-called categorical variables: a
+-- (usually small) finite set of textual values. We map these onto
+-- regular Haskell data types and offer help to generate useful type
+-- class instances for such types.
+module Frames.Streamly.Categorical where
+
+import Prelude hiding (Const)
+--import Control.Applicative (ZipList(..))
+--import Control.DeepSeq (NFData(..))
+--import Control.Monad (MonadPlus(mzero))
+import Data.Char (toUpper)
+import Data.Readable (Readable(..))
+--import Data.Set (Set)
+import qualified Data.Set as S
+--import Data.String (IsString(..))
+--import Data.Text (Text)
+import qualified Data.Text as T
+--import Data.Typeable (Typeable)
+import Data.Vector.Unboxed.Deriving
+import Data.Vinyl.Functor (Const(..))
+--import Data.Word
+import qualified Data.Vector.Unboxed as VU
+import Frames.Streamly.ColumnTypeable -- this is, I think, the only change, so we don't need to update this module at all.
+import Frames.InCore (VectorFor)
+import Frames.ShowCSV
+import Frames.Utils
+import GHC.Exts (Proxy#, proxy#)
+import GHC.TypeNats
+import Language.Haskell.TH
+
+-- | A categorical variable can take on one of a finite number of
+-- textual names. Any value of type @Categorical n@ has no more than
+-- @n@ variants.
+newtype Categorical (n :: Nat) = Categorical { categories :: Set Text }
+  deriving (Eq, Show, Typeable)
+
+-- | Ensure the first character of a 'String' is uppercase.
+cap :: String -> String
+cap [] = []
+cap (c : cs) = toUpper c : cs
+
+-- | Helper for working with 'derivingUnbox'. Takes the name of the
+-- type and the number of variants in the sum type in order to
+-- determine a compact representation.
+unboxDecls :: String -> Int -> DecsQ
+unboxDecls name numVariants =
+  derivingUnbox name
+                [t|() => $(conT (mkName name)) -> $(conT repTy)|]
+                [|fromIntegral . fromEnum|]
+                [|toEnum . fromIntegral|]
+  where repTy
+          | numVariants < 2^(8 :: Int) = ''Word8
+          | numVariants < 2^(16 :: Int) = ''Word16
+          | numVariants < 2^(32 :: Int) = ''Word32
+          | otherwise = ''Word64
+
+-- | Generate a splice with data type declaration and associated
+-- instances for type suitable for representing a categorical
+-- variable. This is a type that maps between a finite set of textual
+-- names and Haskell data constructors. Usage: @declareCategorical
+-- typeName optionalConPrefix variantNames@ will produce a data type
+-- with name @typeName@ and data constructors whose names are a
+-- concatenation of @optionalConPrefix@ and each element of
+-- @variantNames@.
+declareCategorical :: String -> Maybe String -> [String] -> Q [Dec]
+declareCategorical (cap -> name) (fmap cap -> prefix) variants =
+  ([ dataDecl, iIsString, iReadable, iParseable
+   , iShowCSV, iVectorFor, iNFData ] ++)
+  <$> unboxDecls name (length variants)
+  where variantCons = map (mkName . T.unpack . sanitizeTypeName . T.pack . maybe id (++) prefix . cap) variants
+        onVariants :: (String -> Name -> a) -> [a]
+        onVariants f =
+          getZipList (f <$> ZipList variants <*> ZipList variantCons)
+        nameName = mkName . T.unpack . sanitizeTypeName . T.pack $ name
+        fromStringClause variant variantCon =
+          Clause [LitP (StringL variant)] (NormalB (ConE variantCon)) []
+        showCSVClause variant variantCon =
+#if MIN_VERSION_template_haskell(2,18,0)
+          Clause [ConP variantCon [] []]
+#else
+          Clause [ConP variantCon []]
+#endif
+                 (NormalB (AppE (VarE 'T.pack) (LitE (StringL variant))))
+                 []
+        readableGuarded :: Name -> String -> Name -> (Guard, Exp)
+        readableGuarded argName variant variantCon =
+          ( NormalG (InfixE (Just (VarE argName))
+                    (VarE '(==))
+                    (Just (AppE (VarE 'T.pack) (LitE (StringL variant)))))
+          , AppE (VarE 'return ) (ConE variantCon) )
+        dataDecl = DataD [] nameName [] Nothing
+                         (map (flip NormalC []) variantCons)
+                         [DerivClause Nothing [ ConT ''Eq
+                                              , ConT ''Enum
+                                              , ConT ''Bounded
+                                              , ConT ''Ord
+                                              , ConT ''Show ]]
+        iIsString =
+          InstanceD Nothing [] (AppT (ConT ''IsString) (ConT nameName))
+                    [FunD 'fromString
+                          (onVariants fromStringClause)]
+        iReadable =
+          let argName = mkName "t"
+              clauses = onVariants (readableGuarded argName)
+              clausesTotal = clauses ++ [(NormalG (ConE 'True), VarE 'mzero)]
+          in InstanceD Nothing [] (AppT (ConT ''Readable) (ConT nameName))
+                       [FunD 'fromText
+                             [Clause [VarP argName] (GuardedB clausesTotal) []]]
+        iParseable =
+          InstanceD Nothing [] (AppT (ConT ''Parseable) (ConT nameName)) []
+        iShowCSV =
+          InstanceD Nothing [] (AppT (ConT ''ShowCSV) (ConT nameName))
+                    [FunD 'showCSV (onVariants showCSVClause)]
+        iVectorFor =
+#if __GLASGOW_HASKELL__ >= 808
+          TySynInstD (TySynEqn Nothing (AppT (ConT ''VectorFor) (ConT nameName)) (ConT ''VU.Vector))
+#else
+          TySynInstD ''VectorFor (TySynEqn [ConT nameName] (ConT ''VU.Vector))
+#endif
+        iNFData =
+          let argName = mkName "x"
+          in InstanceD Nothing [] (AppT (ConT ''NFData) (ConT nameName))
+                       [FunD 'rnf [Clause [VarP argName]
+                                  (NormalB
+                                   (AppE (AppE (VarE 'seq) (VarE argName))
+                                         (TupE [])))
+                                  []]]
+
+instance KnownNat n => Parseable (Categorical n) where
+  parse txt = return (Possibly (Categorical (S.singleton txt)))
+  parseCombine p1 p2
+    | S.size catCombined <= maxVariants =
+      return (Possibly (Categorical catCombined))
+    | otherwise = mzero
+    where getCats = categories . discardConfidence
+          catCombined = S.union (getCats p1) (getCats p2)
+          maxVariants :: Int
+          maxVariants = fromIntegral (toInteger (natVal' (proxy# :: Proxy# n)))
+  representableAsType (S.toList . categories . discardConfidence -> cats) =
+    Const (Left (\n -> declareCategorical n (Just n) (map T.unpack cats)))
diff --git a/src/Frames/Streamly/ColumnTypeable.hs b/src/Frames/Streamly/ColumnTypeable.hs
new file mode 100644
--- /dev/null
+++ b/src/Frames/Streamly/ColumnTypeable.hs
@@ -0,0 +1,172 @@
+{-# LANGUAGE AllowAmbiguousTypes, BangPatterns,
+             DefaultSignatures, DeriveFunctor,
+             FlexibleInstances,
+             LambdaCase,
+             MultiParamTypeClasses, RankNTypes, ScopedTypeVariables
+             , TemplateHaskell, TypeFamilies #-}
+module Frames.Streamly.ColumnTypeable where
+
+import Prelude hiding (Const(..), Type)
+import Frames.Streamly.OrMissing
+
+import Data.Readable (Readable(fromText))
+import Data.Typeable (typeRep)
+import qualified Data.Text as T
+import Data.Vinyl.Functor (Const(..))
+import Language.Haskell.TH
+
+-- | Type to represent the result of successfully parsing a @Text@ to @a@.
+-- Usually will be @Definitely@ but @Possibly@ may be used if further
+-- parsing to type @a@ may show that @a@ was not possible type for the
+-- entire column.  Used for the @Categorical@ type.
+data Parsed a = Possibly a | Definitely a deriving (Eq, Ord, Show, Functor)
+
+-- | Values that can be read from a 'T.Text' with more or less
+-- discrimination.
+class Parseable a where
+  -- | E.g., if @m@ is 'Maybe',
+  -- Returns 'Nothing' if a value of the given type can not be read;
+  -- returns 'Just Possibly' if a value can be read, but is likely
+  -- ambiguous (e.g. an empty string); returns 'Just Definitely' if a
+  -- value can be read and is unlikely to be ambiguous.
+  parse :: MonadPlus m => T.Text -> m (Parsed a)
+  default parse :: (Readable a, MonadPlus m)
+                => T.Text -> m (Parsed a)
+  parse = fmap Definitely . fromText
+  {-# INLINE parse #-}
+
+  -- | Combine two parse results such that the combination can
+  -- fail. Useful when we have two 'Possibly' parsed values that are
+  -- different enough to suggest the parse of each should be
+  -- considered a failure. The default implementation is to 'return'
+  -- the first argument.
+  parseCombine :: MonadPlus m => Parsed a -> Parsed a -> m (Parsed a)
+  default parseCombine :: MonadPlus m => Parsed a -> Parsed a -> m (Parsed a)
+  parseCombine = const . return
+  {-# INLINE parseCombine #-}
+
+  representableAsType :: Parsed a -> Const (Either (String -> Q [Dec]) Type) a
+  default
+    representableAsType :: Typeable a
+                        => Parsed a -> Const (Either (String -> Q [Dec]) Type) a
+  representableAsType =
+    const (Const (Right (ConT (mkName (show (typeRep (Proxy :: Proxy a)))))))
+  {-# INLINABLE representableAsType #-}
+
+
+-- | Discard any estimate of a parse's ambiguity.
+discardConfidence :: Parsed a -> a
+discardConfidence (Possibly x) = x
+discardConfidence (Definitely x) = x
+{-# INLINE discardConfidence #-}
+
+-- | Acts just like 'fromText': tries to parse a value from a 'T.Text'
+-- and discards any estimate of the parse's ambiguity.
+parse' :: (MonadPlus m, Parseable a) => T.Text -> m a
+parse' = fmap discardConfidence . parse
+{-# INLINE parse' #-}
+
+parseIntish :: (Readable a, MonadPlus f) => T.Text -> f (Parsed a)
+parseIntish t =
+  Definitely <$> fromText (fromMaybe t (T.stripSuffix (T.pack ".0") t))
+{-# INLINEABLE parseIntish #-}
+
+instance Parseable Bool where
+
+instance Parseable Int where
+  parse = parseIntish
+  {-# INLINE parse #-}
+
+instance Parseable Int32 where
+  parse = parseIntish
+  {-# INLINE parse #-}
+
+instance Parseable Int64 where
+  parse = parseIntish
+  {-# INLINE parse #-}
+
+instance Parseable Integer where
+  parse = parseIntish
+  {-# INLINE parse #-}
+
+instance Parseable Float where
+instance Parseable Double where
+  -- Some CSV's export Doubles in a format like '1,000.00', filtering
+  -- out commas lets us parse those sucessfully
+  parse = fmap Definitely . fromText . T.filter (/= ',')
+  {-# INLINE parse #-}
+
+instance Parseable T.Text where
+
+-- @adamConnerSax new/changed stuff
+
+-- | This class relates a universe of possible column types to Haskell
+-- types, and provides a mechanism to infer which type best represents
+-- some textual data.
+-- The type @a@ must somehow represent the list of all possible inferred types and
+-- which of those are currently possible given the data seen so far.
+class ColumnTypeable a where
+  type ParseType a
+  -- ^ An associated type to represent the result of parsing a single @Text@ into possible column types
+  -- represented by @a@
+  type Parsers a
+  -- ^ An associated type to represent information required to parse @Text@ to
+  -- possible types all held in @a@, combine the current slate of possible types and
+  -- a newly parsed set of possibilities and produce template haskell to declare the
+  -- "best" (most specific) type in @a@.
+  colType :: Parsers a -> a -> Either (String -> Q [Dec]) Type
+  -- ^ TH haskell representing the best type among those possible in @a@.
+  -- Used when declaring the type after inference.
+  inferType :: Parsers a -> (T.Text -> Bool) -> T.Text -> ParseType a
+  -- ^ Given parsing info and a function to indicate "missing" data, parse a @Text@ to possible types @a@
+  initialColType :: a
+  -- ^ A value of type @a@ to be used as the initial state in iterative column type inference.
+  -- Usually indicates all types are still possible.
+  updateWithParse :: Parsers a -> a -> ParseType a -> a
+  -- ^ given parsing info, an @a@ representing possible column types given the data so far,
+  -- and a parsed value, indicating possible types of the current item of data, update the
+  -- possible types for the column.
+
+
+instance (Typeable a, Parseable a) => Parseable (OrMissing a) where
+  parse t = return $ maybe (Definitely Missing) (fmap Present) $ parse t
+  {-# INLINEABLE parse #-}
+  parseCombine p1 p2 = case (commute p1, commute p2) of
+    (Missing, Missing) -> return $ Definitely Missing
+    (Present x, Missing) -> return $ fmap Present x
+    (Missing, Present x) -> return $ fmap Present x
+    (Present x, Present y) -> fmap Present <$> parseCombine x y
+    where
+      commute :: Parsed (OrMissing a) -> OrMissing (Parsed a)
+      commute (Possibly Missing) = Missing
+      commute (Possibly (Present a)) = Present (Possibly a)
+      commute (Definitely Missing) = Missing
+      commute (Definitely (Present a)) = Present (Definitely a)
+  {-# INLINEABLE parseCombine #-}
+
+-- | Record-of-functions for column parsing.
+data ParseHow a = ParseHow
+  { phParse :: forall m. MonadPlus m =>  T.Text -> m (Parsed a)
+    -- ^ attempt to parse the given 'Text' into the type @a@
+  , phParseCombine ::  forall m. MonadPlus m => Parsed a -> Parsed a -> m (Parsed a)
+  -- ^ check that two parsed values of type a are both consistent with being of type @a@
+  , phRepresentableAsType :: Parsed a -> Either (String -> Q [Dec]) Type
+  -- ^ Template-Haskell for declaring type @a@.  Either a function to produce a set of
+  -- declarations (for 'Categorical') or the template-haskell 'Type' which can easily be
+  -- turned into a declaration.
+  }
+
+-- | Generate a 'ParseHow' for a any type with a 'Parseable' instance.
+parseableParseHow :: Parseable a => ParseHow a
+parseableParseHow = ParseHow parse parseCombine (getConst . representableAsType)
+{-# INLINEABLE parseableParseHow #-}
+
+-- | Generate a 'ParseHow' for any type with a 'Typeable' instance and a parsing function  of
+-- the form @Text -> Maybe a@.
+simpleParseHow :: forall a . Typeable a => (Text -> Maybe a) -> ParseHow a
+simpleParseHow g = ParseHow p c r where
+  p :: forall n. MonadPlus n =>  T.Text -> n (Parsed a) -- this sig required for MonadPlus constraint
+  p = maybe mzero (return . Definitely) . g
+  c pa _ = return pa
+  r _ = Right (ConT (mkName (show (typeRep (Proxy :: Proxy a)))))
+{-# INLINEABLE simpleParseHow #-}
diff --git a/src/Frames/Streamly/ColumnUniverse.hs b/src/Frames/Streamly/ColumnUniverse.hs
new file mode 100644
--- /dev/null
+++ b/src/Frames/Streamly/ColumnUniverse.hs
@@ -0,0 +1,246 @@
+{-# LANGUAGE AllowAmbiguousTypes, BangPatterns, CPP, ConstraintKinds, DataKinds,
+             DerivingVia,
+             FlexibleContexts, FlexibleInstances, GADTs, InstanceSigs,
+             KindSignatures, LambdaCase, MultiParamTypeClasses,
+             OverloadedStrings, QuasiQuotes, RankNTypes, RecordWildCards,
+             ScopedTypeVariables, StandaloneKindSignatures,
+             TemplateHaskell, TupleSections, TypeApplications,
+             TypeFamilies, TypeOperators, UndecidableInstances #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module Frames.Streamly.ColumnUniverse (
+  CommonColumns
+  , CommonColumnsCat
+  , ParseHowRec
+  , parseableParseHowRec
+  , tryParseAll
+  , CanParseAs(..)
+  , ParseResult(..)
+  , parseResult
+  , parseResult'
+  , ColType(..)
+  , SomeMissing(..)
+  , colTypeTH
+  , colTypeSomeMissing
+  , addParsedCell
+  , initialColType
+) where
+
+import Prelude hiding (Compose(..), Type, getConst, show, Const(..))
+import qualified Data.Text as T
+import Data.Vinyl
+import Data.Vinyl.Functor
+import Frames.Streamly.ColumnTypeable
+import Frames.Streamly.Categorical
+import Language.Haskell.TH
+
+-- | Use a @ParseHow@ to (possibly) parse a given @Text@ as a value of type @a@
+inferParseable :: ParseHow a -> T.Text -> (Maybe :. Parsed) a
+inferParseable ParseHow{..} = Compose . phParse
+{-# INLINE inferParseable #-}
+
+-- | Helper to call 'inferParseable' on a 'Rec'.
+inferParseable' :: ParseHow a -> (((->) T.Text) :. (Maybe :. Parsed)) a
+inferParseable'  = Compose . inferParseable
+{-# INLINE inferParseable' #-}
+
+-- * Record Helpers
+
+type ParseHowRec ts = Rec ParseHow ts
+-- | Generate a Rec of @ParseHow@ for a list of types with 'Parseable' instances.
+parseableParseHowRec :: RPureConstrained Parseable ts => ParseHowRec ts
+parseableParseHowRec = rpureConstrained @Parseable parseableParseHow
+{-# INLINEABLE parseableParseHowRec #-}
+
+-- | Try to parse the given 'Text' to each type in a type-list, putting the result in a Rec.
+tryParseAll :: forall ts. (RMap ts)
+            => ParseHowRec ts -> T.Text -> Rec (Maybe :. Parsed) ts
+tryParseAll phR = rtraverse getCompose funs
+  where funs :: Rec (((->) T.Text) :. (Maybe :. Parsed)) ts
+        funs = rmap inferParseable' phR
+{-# INLINABLE tryParseAll #-}
+
+-- * Common Columns
+
+-- | Common column types: 'Bool', 'Int', 'Double', 'T.Text'
+type CommonColumns = [Bool, Int, Double, T.Text]
+
+-- | Common column types including categorical types.
+-- NB: If @Categorical 8@ is too small for some types you can
+-- increase the number or *add* a larger 'Categorical' after. e.g.,
+-- @MoreColumnsCat = [Bool, Int, Double, Categorical 8, Categorical 32, T.Text]@
+type CommonColumnsCat = [Bool, Int, Double, Categorical 8, T.Text]
+
+-- | 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.
+--type Columns = ColumnUniverse CommonColumns
+
+-- @adamConnerSax new stuff
+-- | Type to flag whether some data in a column is missing.
+data SomeMissing = SomeMissing | NoneMissing deriving (Eq, Show)
+
+-- | Type to hold the result of an attempt to parse at each possible type.
+-- Isomorphic to @Maybe (Parsed p)@ but makes the code clearer.
+data CanParseAs p = YesParse (Parsed p) | NoParse
+
+-- | Given the 'ParseHow' record-of-functions for @p@, combine two possible
+-- parses of @p@ into a possible parse of @p@.  Largely, this amounts to
+-- success if both succeeded and failure if either failed.  But for some types,
+-- e.g., 'Categorical` two previous parses can succeed but the combination can fail.
+-- In the case of `Categorical` that can happen because we discover, for example, a
+-- 9th alternative when we are trying to parse as @Categorical 8@. In general, this
+-- could be useful anytime the success or failure of parsing a column as type @p@
+-- depends on some property of the set of all values present in the column rather
+-- than just if each single parse attempt succeeds.
+combineCanParseAs :: ParseHow p -> CanParseAs p -> CanParseAs p -> CanParseAs p
+combineCanParseAs ParseHow{..} (YesParse x) (YesParse y) = maybe NoParse YesParse $ phParseCombine x y
+combineCanParseAs _ _ _ = NoParse
+
+-- | Holder for the results of attempting to parse as all the types
+-- in the type-list @ts@.  Isomorphic to @Maybe (Rec CanParseAs ts)@
+data ParseResult ts = MissingData | ParseResult (Rec CanParseAs ts)
+
+-- | Given a 'ParseHow' for each type in @ts@ and a function to indicate which data is "missing"
+-- attempt to parse a given 'Text' as each type in @ts@ and return the result as possible
+-- parses at each type.
+parseResult' :: RMap ts => ParseHowRec ts -> (Text -> Bool) -> Text -> ParseResult ts
+parseResult' phRec missingF t
+  | missingF t = MissingData
+  | otherwise = ParseResult $ recParsedToRecCanParseAs $ tryParseAll phRec t
+{-# INLINEABLE parseResult' #-}
+
+-- | Given a 'ParseHow' for each type in @ts@,
+-- attempt to parse a given 'Text' as each type in @ts@ and return the result as possible
+-- parses at each type.
+-- NB: Uses a default definition of "missing" data: "" or "NA"
+parseResult ::  RMap ts => ParseHowRec ts -> Text -> ParseResult ts
+parseResult phR = parseResult' phR defaultMissing where
+  defaultMissing t = T.null t || t == "NA"
+{-# INLINEABLE parseResult #-}
+
+
+-- | Helper for converting @Maybe :. Parsed@ to  'CanParseAs'
+recParsedToRecCanParseAs :: RMap ts => Rec (Maybe :. Parsed) ts -> Rec CanParseAs ts
+recParsedToRecCanParseAs = rmap (parsedToCanParseAs . getCompose)
+{-# INLINEABLE recParsedToRecCanParseAs #-}
+
+-- | Helper for converting @Maybe (Parsed a)@ to  @CanParseAs a@
+parsedToCanParseAs :: Maybe (Parsed a) -> CanParseAs a
+parsedToCanParseAs Nothing = NoParse
+parsedToCanParseAs (Just x)  = YesParse x
+{-# INLINE parsedToCanParseAs #-}
+
+-- | Type to hold parsing information for a single column
+-- It starts completely unknown (@UnknownColType NoneMissing@)
+-- and remains completely unknown if the data starts off missing
+-- (@UnknownColType SomeMissing@).  Once we begin seeing non-missing
+-- items we hold those results and whether or not we have encountered
+-- any missing data, in @KnownColType@
+data ColType ts = UnknownColType SomeMissing
+                | KnownColType (SomeMissing, Rec CanParseAs ts)
+
+type ColTH = Either (String -> Q [Dec]) Type
+type ColTHF = Lift (->) CanParseAs (Const (Maybe ColTH))
+
+-- | Given a 'Rec' of 'parseHow', build a @Rec@ of functions
+-- which will map parse results for each type to the template-haskell
+-- required to declare that type.
+colTHs :: RMap ts
+       => ParseHowRec ts
+       -> Rec ColTHF ts
+colTHs = rmap f where
+  f :: ParseHow a -> ColTHF a
+  f ParseHow{..} =  Lift $ \x -> Const $ case x of
+    NoParse ->  Nothing
+    YesParse a -> Just $ phRepresentableAsType a
+{-# INLINE colTHs #-}
+
+-- | Our default is always to declare a column as 'Text'
+-- so we fall back to this in various situations where the
+-- correct type is unclear.
+fallbackText :: ColTH
+fallbackText = Right (ConT (mkName "Text"))
+{-# INLINE fallbackText #-}
+
+instance ( RFoldMap ts
+         , RMap ts
+         , RApply ts)
+     => ColumnTypeable (ColType ts) where
+  type ParseType (ColType ts) = ParseResult ts
+  type Parsers (ColType ts) = ParseHowRec ts
+  colType = colTypeTH
+  {-# INLINEABLE colType #-}
+  inferType = parseResult'
+  {-# INLINEABLE inferType #-}
+  initialColType = UnknownColType NoneMissing
+  {-# INLINE initialColType #-}
+  updateWithParse = addParsedCell
+  {-# INLINE updateWithParse #-}
+
+-- | Given a 'Rec' of 'ParseHow' and the inference information in @ColType ts@,
+-- produce the the template-haskell for the first type in the type-list @ts@ where
+-- parsing succeeded.
+-- This suggests an ordering of types in the list from most-to-least specific with
+-- 'Text' always last.
+-- NB: When using 'Categorical' this ordering requires some thought!  If you want
+-- 'Categorical' for alternatives which cannot be parsed as anything but 'Text', you
+-- should put your `Categorical'(s) (in increasing order of size) right before 'Text'
+-- at the end of the list.  But if you have columns using integers to code Categorical
+-- values you might want a Categorical *before* 'Int'.
+colTypeTH :: ( RFoldMap ts
+             , RMap ts
+             , RApply ts)
+          => ParseHowRec ts -> ColType ts -> Either (String -> Q [Dec]) Type
+colTypeTH phR t =  case t of
+    UnknownColType _ -> fallbackText
+    KnownColType (_,ts) ->
+      fromMaybe fallbackText $ getFirst $ rfoldMap (First . getConst) $ rapply (colTHs phR) ts
+{-# INLINEABLE colTypeTH #-}
+
+-- | Extract the 'SomeMissing' flag from a 'ColType'
+colTypeSomeMissing :: ColType ts -> SomeMissing
+colTypeSomeMissing (UnknownColType x) = x
+colTypeSomeMissing (KnownColType (x, _)) = x
+{-# INLINE colTypeSomeMissing #-}
+
+-- | Given a @Rec ParseHow ts@, combine previous parsing information, a @ColType ts@ representing
+-- possible parses of all the data seen so far, with the possible parses of a new piece of data,
+-- represented by @ParseResult ts@ and produce a new set of possible parses for the entire column
+-- so far.
+addParsedCell :: (RMap ts, RApply ts) => ParseHowRec ts -> ColType ts -> ParseResult ts -> ColType ts
+addParsedCell _ (UnknownColType _) MissingData = UnknownColType SomeMissing
+addParsedCell _ (UnknownColType sm) (ParseResult pRec) = KnownColType (sm, pRec)
+addParsedCell _ (KnownColType (_, ctRec)) MissingData = KnownColType (SomeMissing, ctRec)
+addParsedCell phR (KnownColType (sm, ctRec)) (ParseResult pRec) = KnownColType (sm, newCtRec)
+  where
+    newCtRec = rzipWith3 combineCanParseAs phR pRec ctRec
+{-# INLINEABLE addParsedCell #-}
+
+-- | rzipWith extended to 3 argument functions
+rzipWith3 :: forall f g h q ts. (RMap ts, RApply ts)
+          => (forall x. f x -> g x -> h x -> q x)
+          -> Rec f ts
+          -> Rec g ts
+          -> Rec h ts
+          -> Rec q ts
+rzipWith3 f fs gs = rapply appliedFG where
+  appliedFG :: Rec (Lift (->) h q) ts
+  appliedFG = rzipWith liftFG fs gs where
+    liftFG :: f x -> g x -> Lift (->) h q x
+    liftFG fx  = Lift . f fx
+{-# INLINEABLE rzipWith3 #-}
+
+data SimpleDict c a where
+  SimpleDict :: c a => SimpleDict c a
+
+-- | rzipWith but with a constraint in-scope when the zipping function is applied.
+rzipWithC ::forall c ts f g h. (RMap ts, RApply ts, RPureConstrained c ts)
+          => (forall x.c x => f x -> g x -> h x)
+          -> Rec f ts
+          -> Rec g ts
+          -> Rec h ts
+rzipWithC l = rzipWith3 q (rpureConstrained @c SimpleDict) where
+  q :: SimpleDict c x -> f x -> g x -> h x
+  q SimpleDict = l
+{-# INLINEABLE rzipWithC #-}
diff --git a/src/Frames/Streamly/InCore.hs b/src/Frames/Streamly/InCore.hs
--- a/src/Frames/Streamly/InCore.hs
+++ b/src/Frames/Streamly/InCore.hs
@@ -1,10 +1,13 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
 {-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE CPP #-}
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE DerivingStrategies #-}
 {-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RankNTypes        #-}
+{-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE StrictData #-}
 {-# LANGUAGE TupleSections #-}
@@ -40,95 +43,69 @@
     )
 where
 
-#if MIN_VERSION_streamly(0,8,0)
-#else
-import qualified Streamly
-#endif
-import qualified Streamly.Prelude                       as Streamly
-import qualified Streamly.Data.Fold                     as Streamly.Fold
-import qualified Streamly.Internal.Data.Fold            as Streamly.Fold
+import qualified Frames
+import qualified Frames.InCore                          as Frames
+import           Frames.InCore                           (VectorFor, VectorMFor, VectorMs, Vectors, RecVec(..), toAoS)
 
+import Frames.Streamly.Streaming.Class (StreamFunctions(..)) --, FoldType)
 import qualified Control.Monad.Primitive                as Prim
 
 import qualified Data.Vinyl                             as Vinyl
 
-import qualified Frames
-import qualified Frames.InCore                          as Frames
-import           Frames.InCore                           (VectorFor, VectorMFor, VectorMs, Vectors, RecVec(..), toAoS)
 
-
 -- | Fold a stream of 'Vinyl' records into SoA (Structure-of-Arrays) form.
 -- Here as a 'streamly' fold, so it may be deployed along with other folds or on only part of a stream.
-#if MIN_VERSION_streamly(0,8,0)
-inCoreSoA_F :: forall m rs. (Prim.PrimMonad m, Frames.RecVec rs)
-          => Streamly.Fold.Fold m (Frames.Record rs) (Int, Vinyl.Rec (((->) Int) Frames.:. Frames.ElField) rs)
-inCoreSoA_F = Streamly.Fold.mkFoldM feed initial fin
-  where feed (!i, !sz, !mvs') row
-          | i == sz = Frames.growRec (Proxy::Proxy rs) mvs'
-                      >>= flip feed row . (i, sz*2,)
-          | otherwise = do Frames.writeRec (Proxy::Proxy rs) i mvs' row
-                           return $ Streamly.Fold.Partial (i+1, sz, mvs')
-
-        initial = do
-          mvs <- Frames.allocRec (Proxy :: Proxy rs) Frames.initialCapacity
-          return $ Streamly.Fold.Partial (0, Frames.initialCapacity, mvs)
-
-        fin (n, _, mvs') =
-          do vs <- Frames.freezeRec (Proxy::Proxy rs) n mvs'
-             return . (n,) $ Frames.produceRec (Proxy::Proxy rs) vs
-#else
-inCoreSoA_F :: forall m rs. (Prim.PrimMonad m, Frames.RecVec rs)
-          => Streamly.Fold.Fold m (Frames.Record rs) (Int, Vinyl.Rec (((->) Int) Frames.:. Frames.ElField) rs)
-inCoreSoA_F = Streamly.Fold.mkFold feed initial fin
-  where feed (!i, !sz, !mvs') row
-          | i == sz = Frames.growRec (Proxy::Proxy rs) mvs'
-                      >>= flip feed row . (i, sz*2,)
-          | otherwise = do Frames.writeRec (Proxy::Proxy rs) i mvs' row
-                           return (i+1, sz, mvs')
+inCoreSoA_F :: forall rs s m. (Prim.PrimMonad m, Frames.RecVec rs, StreamFunctions s m)
+             => FoldType s m (Frames.Record rs) (Int, Vinyl.Rec (((->) Int) Frames.:. Frames.ElField) rs)
+inCoreSoA_F = sBuildFoldM @s feed initial fin where
+  feed (!i, !sz, !mvs') row
+    | i == sz = Frames.growRec (Proxy::Proxy rs) mvs'
+                >>= flip feed row . (i, sz*2,)
+    | otherwise = do Frames.writeRec (Proxy::Proxy rs) i mvs' row
+                     return (i+1, sz, mvs')
 
-        initial = do
-          mvs <- Frames.allocRec (Proxy :: Proxy rs) Frames.initialCapacity
-          return (0, Frames.initialCapacity, mvs)
+  initial = do
+    mvs <- Frames.allocRec (Proxy :: Proxy rs) Frames.initialCapacity
+    return (0, Frames.initialCapacity, mvs)
 
-        fin (n, _, mvs') =
-          do vs <- Frames.freezeRec (Proxy::Proxy rs) n mvs'
-             return . (n,) $ Frames.produceRec (Proxy::Proxy rs) vs
-#endif
-{-# INLINE inCoreSoA_F #-}
+  fin (n, _, mvs') =
+    do vs <- Frames.freezeRec (Proxy::Proxy rs) n mvs'
+       return . (n,) $ Frames.produceRec (Proxy::Proxy rs) vs
+{-# INLINEABLE inCoreSoA_F #-}
 
 -- | Perform the 'inCoreSoA_F' fold on a stream of records.
-inCoreSoA :: forall m rs. (Prim.PrimMonad m, Frames.RecVec rs)
-          => Streamly.SerialT m (Frames.Record rs)
+inCoreSoA :: forall rs s m. (Prim.PrimMonad m, Frames.RecVec rs, StreamFunctions s m)
+          => s m (Frames.Record rs)
           -> m (Int, Vinyl.Rec (((->) Int) Frames.:. Frames.ElField) rs)
-inCoreSoA = Streamly.fold inCoreSoA_F
-{-# INLINE inCoreSoA #-}
+inCoreSoA = sFold (inCoreSoA_F @rs @s)
+{-# INLINEABLE inCoreSoA #-}
 
 -- | Fold a stream of 'Vinyl' records into AoS (Array-of-Structures) form.
-inCoreAoS_F :: forall m rs. (Prim.PrimMonad m, Frames.RecVec rs)
-          => Streamly.Fold.Fold m (Frames.Record rs) (Frames.FrameRec rs)
-inCoreAoS_F = fmap (uncurry Frames.toAoS) inCoreSoA_F
+inCoreAoS_F :: forall rs s m. (Prim.PrimMonad m, Frames.RecVec rs, StreamFunctions s m)
+          => FoldType s m (Frames.Record rs) (Frames.FrameRec rs)
+inCoreAoS_F = sMapFoldM @s (return . uncurry Frames.toAoS) $ inCoreSoA_F @rs @s
 {-# INLINE inCoreAoS_F #-}
 
 -- | Perform the 'inCoreAoS_F' fold on a stream of records.
-inCoreAoS :: forall m rs. (Prim.PrimMonad m, Frames.RecVec rs)
-          => Streamly.SerialT m (Frames.Record rs)
+inCoreAoS :: forall m rs s. (Prim.PrimMonad m, Frames.RecVec rs, StreamFunctions s m)
+          => s m (Frames.Record rs)
           -> m (Frames.FrameRec rs)
-inCoreAoS = Streamly.fold inCoreAoS_F --fmap (uncurry Frames.toAoS) . inCoreSoA
-{-# INLINE inCoreAoS #-}
+inCoreAoS = sFold (inCoreAoS_F @rs @s)
+{-# INLINEABLE inCoreAoS #-}
 
 
 -- | More general AoS fold, allowing for a, possible column changing, transformation of the records while in SoA form.
-inCoreAoS'_F ::  forall ss rs m. (Prim.PrimMonad m, Frames.RecVec rs)
-           => (Frames.Rec ((->) Int Frames.:. Frames.ElField) rs -> Frames.Rec ((->) Int Frames.:. Frames.ElField) ss)
-           -> Streamly.Fold.Fold m (Frames.Record rs) (Frames.FrameRec ss)
-inCoreAoS'_F f  = fmap (uncurry Frames.toAoS . aux) inCoreSoA_F
+inCoreAoS'_F ::  forall ss rs s m. (Prim.PrimMonad m, Frames.RecVec rs, StreamFunctions s m)
+             => (Frames.Rec ((->) Int Frames.:. Frames.ElField) rs -> Frames.Rec ((->) Int Frames.:. Frames.ElField) ss)
+             -> FoldType s m (Frames.Record rs) (Frames.FrameRec ss)
+inCoreAoS'_F f  = sMapFoldM @s (return . uncurry Frames.toAoS . aux) (inCoreSoA_F @rs @s)
   where aux (x,y) = (x, f y)
-{-# INLINE inCoreAoS'_F #-}
+{-# INLINEABLE inCoreAoS'_F #-}
 
 -- | Perform the more general AoS fold on a stream of records.
-inCoreAoS' ::  forall ss rs m. (Prim.PrimMonad m, Frames.RecVec rs)
+inCoreAoS' ::  forall ss rs s m. (Prim.PrimMonad m, Frames.RecVec rs,  StreamFunctions s m)
            => (Frames.Rec ((->) Int Frames.:. Frames.ElField) rs -> Frames.Rec ((->) Int Frames.:. Frames.ElField) ss)
-           -> Streamly.SerialT m (Frames.Record rs)
+           -> s m (Frames.Record rs)
            -> m (Frames.FrameRec ss)
-inCoreAoS' f = Streamly.fold (inCoreAoS'_F f)
-{-# INLINE inCoreAoS' #-}
+inCoreAoS' f = sFold (inCoreAoS'_F @ss @rs @s f)
+{-# INLINEABLE inCoreAoS' #-}
diff --git a/src/Frames/Streamly/Internal/CSV.hs b/src/Frames/Streamly/Internal/CSV.hs
new file mode 100644
--- /dev/null
+++ b/src/Frames/Streamly/Internal/CSV.hs
@@ -0,0 +1,139 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveLift #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# Language GADTs #-}
+{-# LANGUAGE TypeFamilies #-}
+{-|
+Module      : Frames.Streamly.Internal.CSV
+Description : Internal module to make the HeaderList opaque
+Copyright   : (c) Adam Conner-Sax 2021
+License     : BSD-3-Clause
+Maintainer  : adam_conner_sax@yahoo.com
+Stability   : experimental
+
+-}
+module Frames.Streamly.Internal.CSV
+  (
+    -- * Types
+    -- ** Text Wrappers
+    HeaderText(..)
+  , ColTypeName(..)
+    -- ** Type-Level representation for Column Id
+  , ColumnState(..)
+  , ColumnId(..)
+  , ColumnIdType
+    -- ** For RowGen
+  , RowGenColumnSelector(..)
+  , OrMissingWhen(..)
+  , MissingRequiredIdsF
+    -- **  For ParserOptions
+  , ParseColumnSelector(..)
+  -- * Combinators
+  -- ** Building RowGen Column Selectors
+  , modifyColumnSelector
+  -- ** Building ParserOptions from RowGen and File
+  , includedHeaders
+  , includedColTypeNames
+  , includedColTypeInfo
+  , colStatesAndHeadersToParseColHandler
+  -- * Exceptions
+  , FramesCSVException(..)
+  )
+where
+
+import Language.Haskell.TH.Syntax (Lift)
+
+-- | Unite the various exceptions which might arise during TH inference of
+-- column types or parsing of the file into a Frame.
+data FramesCSVException =
+  EmptyStreamException
+  | MissingHeadersException [HeaderText]
+  | BadHeaderException Text
+  | WrongNumberColumnsException Text deriving Show
+
+instance Exception FramesCSVException
+
+-- | Type to control how column type inference is handled,
+-- particularly in the presence of missing data
+data OrMissingWhen = NeverMissing -- ^ Infer column type solely from non-missing data,
+                   | AlwaysPossible -- ^ Infer column type as @Maybe a@ where @a@ is inferred from non-missing data.
+                   | IfSomeMissing -- ^ Infer column type as @a@ if none is missing and @Maybe a@ otherwise.
+               deriving (Show, Eq, Lift)
+
+
+-- | Wrapper for the text value of the column header to match on in column selection
+newtype HeaderText = HeaderText { headerText :: Text } deriving (Show, Eq, Ord, Lift)
+
+-- | Wrapper for the text of the type name we are generating for a column.
+-- NB: This might be almost the same as 'HeaderText' (up to capitalization, etc.)
+-- But it might be different:
+-- 1. Due to prefixing or renaming.
+-- 2. If we ignore the header or don't have one, then this name comes from the
+-- column position rather than the header text.
+newtype ColTypeName = ColTypeName { colTypeName :: Text } deriving (Show, Eq, Ord, Lift)
+
+-- | Per-column indicator of exclusion or type-name to generate when included.
+-- Isomorphic to @Maybe ColTypeName@ but clearer in use.
+data ColumnState = Exclude | Include (ColTypeName, OrMissingWhen) deriving (Eq, Show, Lift)
+
+-- | Type to index column selection and naming behavior
+data ColumnId = ColumnByName | ColumnByPosition
+
+
+-- | Map from 'ColumnId' type to the inhabited type used for
+-- column inclusion/exclusion and type-naming.
+type family ColumnIdType (a :: ColumnId) :: Type where
+  ColumnIdType 'ColumnByName = HeaderText
+  ColumnIdType 'ColumnByPosition = Int
+
+type MissingRequiredIdsF (a :: ColumnId) =  [ColumnIdType a] -> [ColumnIdType a]
+-- For RowGen
+
+-- |  Type to specify how columns are selected when types are generated
+-- by tableTypes.  Types can be generated from header text or column position.
+-- This type is parameterized by that choice.
+data RowGenColumnSelector (a :: ColumnId) where
+  GenUsingHeader :: (HeaderText -> ColumnState) -> MissingRequiredIdsF 'ColumnByName -> RowGenColumnSelector 'ColumnByName
+  GenIgnoringHeader :: (Int -> ColumnState) -> MissingRequiredIdsF 'ColumnByPosition  -> RowGenColumnSelector 'ColumnByPosition
+  GenWithoutHeader ::  (Int -> ColumnState) ->  MissingRequiredIdsF 'ColumnByPosition -> RowGenColumnSelector 'ColumnByPosition
+
+-- | combinator to update or switch out the column selection function of a RowGenColumnSelector
+modifyColumnSelector :: RowGenColumnSelector a
+                     -> ((ColumnIdType a -> ColumnState) -> (ColumnIdType a -> ColumnState))
+                     -> (MissingRequiredIdsF a -> MissingRequiredIdsF a)
+                     -> RowGenColumnSelector a
+modifyColumnSelector (GenUsingHeader f mr) g h = GenUsingHeader (g f) (h mr)
+modifyColumnSelector (GenIgnoringHeader f mr) g h = GenIgnoringHeader (g f) (h mr)
+modifyColumnSelector (GenWithoutHeader f mr) g h = GenWithoutHeader (g f) (h mr)
+
+
+-- | Type to control how the csv parsers include/exclude columns
+data ParseColumnSelector =
+  ParseAll Bool -- ^ True if there's a header and false if not
+  | ParseUsingHeader [HeaderText]
+  | ParseIgnoringHeader [ColumnState]
+  | ParseWithoutHeader [ColumnState] deriving (Lift, Show)
+
+-- Helpers for generating the Correct ParseColumnSelector
+includedHeaders :: [ColumnState] -> [HeaderText] -> [HeaderText]
+includedHeaders cs hs = catMaybes $ fmap f $ zip hs cs where
+  f (_, Exclude) = Nothing
+  f (x, Include _) = Just x
+{-# INLINEABLE includedHeaders #-}
+
+includedColTypeNames :: [ColumnState] -> [ColTypeName]
+includedColTypeNames = mapMaybe f where
+  f Exclude = Nothing
+  f (Include (x, _)) = Just x
+{-# INLINEABLE includedColTypeNames #-}
+
+includedColTypeInfo :: [ColumnState] -> [(ColTypeName, OrMissingWhen)]
+includedColTypeInfo = mapMaybe f where
+  f Exclude = Nothing
+  f (Include x) = Just x
+{-# INLINEABLE includedColTypeInfo #-}
+
+
+colStatesAndHeadersToParseColHandler :: [ColumnState] -> [HeaderText] -> ParseColumnSelector
+colStatesAndHeadersToParseColHandler cs hs = ParseUsingHeader $ includedHeaders cs hs
+{-# INLINEABLE colStatesAndHeadersToParseColHandler #-}
diff --git a/src/Frames/Streamly/LoadInCore.hs b/src/Frames/Streamly/LoadInCore.hs
new file mode 100644
--- /dev/null
+++ b/src/Frames/Streamly/LoadInCore.hs
@@ -0,0 +1,46 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+{-|
+Module      : Frames.Streamly.LoadInCore
+Description : Load directly from file to Frame
+Copyright   : (c) Adam Conner-Sax 2021
+License     : BSD-3-Clause
+Maintainer  : adam_conner_sax@yahoo.com
+Stability   : experimental
+
+-}
+module Frames.Streamly.LoadInCore
+    (
+      loadInCore
+--    , loadInCore2
+    )
+where
+
+import qualified Frames
+import qualified Data.Vinyl as V
+import qualified Frames.Streamly.InCore                          as FS
+import qualified Frames.Streamly.CSV                          as FS
+--import qualified Data.Strict.Maybe as Strict.Maybe
+import Frames.Streamly.Streaming.Class (StreamFunctions(..), StreamFunctionsIO(..))
+
+loadInCore :: forall s m rs rs'.(StreamFunctionsIO s m, V.RMap rs, FS.StrictReadRec rs, FS.RecVec rs, FS.RecVec rs')
+           => FS.ParserOptions
+           -> FilePath
+           -> (Frames.Record rs -> Maybe (Frames.Record rs'))
+           -> (IOSafe s m) (Frames.FrameRec rs')
+loadInCore po fp t = FS.inCoreAoS $ sMapMaybe t $ FS.readTableOpt @rs @s @m po fp
+{-# INLINEABLE loadInCore #-}
+
+{-
+loadInCore2 :: forall s m rs rs'.(StreamFunctionsIO s m, V.RMap rs, FS.StrictReadRec rs, FS.RecVec rs, FS.RecVec rs')
+           => FS.ParserOptions -> FilePath -> (Frames.Record rs -> Maybe (Frames.Record rs')) -> (IOSafe s m) (Frames.FrameRec rs')
+loadInCore2 po fp t = sReadScanMAndFold @s @m fp (FS.parsingScanF po $ FS.parseOne po) (return FS.AccInitial) fld where
+  fromScan :: FS.Acc (Frames.Record rs) -> Maybe (Frames.Record rs')
+  fromScan x = FS.accToMaybe x >>= t
+  {-# INLINE fromScan #-}
+  fld :: FoldType s (IOSafe s m) (FS.Acc (Frames.Record rs)) (Frames.FrameRec rs')
+  fld = sLMapFoldM @s (return . fromScan) $ sFoldMaybe @s (FS.inCoreAoS_F @_ @s @(IOSafe s m))
+  {-# INLINE fld #-}
+{-# INLINE loadInCore2 #-}
+-}
diff --git a/src/Frames/Streamly/OrMissing.hs b/src/Frames/Streamly/OrMissing.hs
new file mode 100644
--- /dev/null
+++ b/src/Frames/Streamly/OrMissing.hs
@@ -0,0 +1,121 @@
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE TypeFamilies #-}
+module Frames.Streamly.OrMissing
+  (
+    OrMissing(..)
+  , toMaybe
+  , toOrMissing
+  , orMissing
+  , derivingOrMissingUnboxVectorFor
+  , derivingOrMissingUnboxVectorFor'
+  -- * re-export derivingUnbox for ergonomics
+  , derivingUnbox
+    -- * re-exports for deriving
+  , module Data.Vector.Unboxed -- for Vector
+  , module Frames.Streamly.InCore -- for VectorFor
+  )
+where
+
+import Frames.Streamly.InCore as FStreamly
+import Frames.Streamly.InCore (VectorFor)
+
+import Prelude hiding (Type, lift)
+import qualified Data.Text as T
+import Data.Vector.Unboxed.Deriving
+import Data.Vector.Unboxed (Unbox)
+import qualified Data.Vector as Vec
+import qualified Data.Vector.Unboxed as UVec
+import Data.Vector.Unboxed (Vector)
+import Language.Haskell.TH
+
+-- | Represent data that may be present or missing in a data-set, but is definitely of type @a@
+-- This is isomorphic to Maybe but a different type is used to avoid orphan instances for
+-- @Unbox@ and type instance @VectorFor@
+data OrMissing a = Missing | Present a deriving (Show, Eq, Ord, Generic, Functor, Typeable)
+
+toMaybe :: OrMissing a -> Maybe a
+toMaybe Missing = Nothing
+toMaybe (Present a) = Just a
+{-# INLINE toMaybe #-}
+
+toOrMissing :: Maybe a -> OrMissing a
+toOrMissing Nothing = Missing
+toOrMissing (Just a) = Present a
+{-# INLINE toOrMissing #-}
+
+orMissing :: a -> (b -> a) -> OrMissing b -> a
+orMissing defA toA = \case
+  Missing -> defA
+  Present b -> toA b
+{-# INLINE orMissing #-}
+
+derivingUnbox
+  "OrMissingInt"
+  [t|OrMissing Int -> (Bool, Int)|]
+  [e|orMissing (False, 0)(\x -> (True, x))|]
+  [e|\(b, x) -> if b then Present x else Missing|]
+
+derivingUnbox
+  "OrMissingBool"
+  [t|OrMissing Bool -> (Bool, Bool)|]
+  [e|orMissing (False, False)(\x -> (True, x))|]
+  [e|\(b, x) -> if b then Present x else Missing|]
+
+derivingUnbox
+  "OrMissingDouble"
+  [t|OrMissing Double -> (Bool, Double)|]
+  [e|orMissing (False, 0)(\x -> (True, x))|]
+  [e|\(b, x) -> if b then Present x else Missing|]
+
+type instance FStreamly.VectorFor (OrMissing Bool) = UVec.Vector
+type instance FStreamly.VectorFor (OrMissing Int) = UVec.Vector
+type instance FStreamly.VectorFor (OrMissing Double) = UVec.Vector
+type instance FStreamly.VectorFor (OrMissing Text) = Vec.Vector
+
+
+-- | Derive Unbox instance and the corresponding type family instance for
+-- @VectorFor@ and @Unbox@.  Requires @VectorFor@ (from Frames)
+-- and @Vector@ (from Data.Vector.Unbox)
+-- to be in scope.
+-- NB: Importing this module unqualified will put the correct things in scope.
+-- NB: This may conflict with other vector imports which also use the @Vector@ name.
+-- @Data a@ is required to avoid requiring @Lift a@ which causes stage-restriction issues.
+derivingOrMissingUnboxVectorFor :: Text -- ^ Unique constructor suffix for the MVector and Vector data families.  Usually the type name.
+                                -> ExpQ -- ^ TH expression for a default value of the type. E.g. @[e|MyType 0|]@
+                                -> DecsQ -- ^ declarations for the @Unbox@ and @VectorFor@ instances
+derivingOrMissingUnboxVectorFor name defAExpQ = do
+  let nameQ = conT (mkName $ T.unpack name)
+      typQ = [t|() => OrMissing $(nameQ) -> (Bool, $(nameQ))|]
+--      anyAName = mkName "anyA" -- this name must match the argument.
+      expNothingQ :: Q Exp = tupE [return $ ConE 'False, defAExpQ]
+      srcToRepExpQ = lamE [] $ appE (appE [e|orMissing|] expNothingQ) [e|\x->(True,x)|]
+      repToSrcExpQ = [e|\(b, x) -> if b then Present x else Missing|]
+  unboxDecs <- derivingUnbox (T.unpack $ "OrMissing" <> name) typQ srcToRepExpQ repToSrcExpQ
+  vectorDecs <- [d|type instance VectorFor (OrMissing $(nameQ)) = Vector|]
+  return $ unboxDecs <> vectorDecs
+
+
+-- | Derive @Unbox@ instance and the corresponding type family instance
+-- @VectorFor@ for @a@ *and* @OrMissing a@.  Requires @VectorFor@ (from Frames)
+-- and @Vector@ (from Data.Vector.Unbox)
+-- to be in scope.
+-- NB: Importing this module *unqualified* will put the correct things in scope.
+-- NB: This may conflict with other vector imports which also use the @Vector@ name.
+derivingOrMissingUnboxVectorFor' :: DecsQ
+                                 -> Text -- ^ Unique constructor suffix for the MVector and Vector data families.  Must be the type name.
+                                 -> ExpQ -- ^ TH expression for a default value of the type. E.g. @[e|EnumA|]@
+                                 -> DecsQ -- ^ declarations for the @Unbox@ and @VectorFor@ instances
+derivingOrMissingUnboxVectorFor' underlyingUnboxDecsQ name defAExpQ = do
+  let nameQ = conT (mkName $ T.unpack name)
+  underlyingUnboxDecs <-  underlyingUnboxDecsQ
+  underlyingVectorDecs <- [d|type instance VectorFor $(nameQ) = Vector|]
+  unboxAndVectorDecs <- derivingOrMissingUnboxVectorFor name defAExpQ
+  return $ underlyingUnboxDecs <> underlyingVectorDecs <> unboxAndVectorDecs --unboxDecs <> vectorDecs
diff --git a/src/Frames/Streamly/Streaming/Class.hs b/src/Frames/Streamly/Streaming/Class.hs
new file mode 100644
--- /dev/null
+++ b/src/Frames/Streamly/Streaming/Class.hs
@@ -0,0 +1,126 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveLift #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE TypeFamilies #-}
+module Frames.Streamly.Streaming.Class where
+
+import Frames.Streamly.Streaming.Common (Separator, QuotingMode)
+import           Control.Monad.Catch                     ( MonadThrow(..))
+import Control.Foldl (PrimMonad)
+
+class Monad m => StreamFunctions (s :: (Type -> Type) -> Type -> Type) (m :: Type -> Type) where
+  type FoldType s ::  (Type -> Type) -> Type -> Type -> Type
+  sThrowIfEmpty :: forall x. MonadThrow m => s m x -> m ()
+  -- ^ throw an exception if the stream is empty
+  sLength :: s m a -> m Int
+  -- ^ return the length of the stream
+  sCons :: forall a. a -> s m a -> s m a
+  -- ^ add an element to the head of a stream
+  sUncons :: forall a . s m a -> m (Maybe (a, s m a))
+  -- ^ split a stream into it's head and tail, returning @m Nothing@ if the stream was empty
+  sHead :: forall a . s m a -> m (Maybe a)
+  -- ^ return the first item of a (possibly empty) stream. @m Nothing@ if the stream was empty
+  sMap :: forall x y. (x -> y) -> s m x -> s m y
+  -- ^ map each element of the stream using the given function
+  sMapMaybe :: forall x y. (x -> Maybe y) -> s m x -> s m y
+  -- ^ map each element of the stream using the given function
+  sScanM :: forall a x.(x -> a -> m x) -> m x -> s m a -> s m x
+  -- ^ map each item along with a running state to produce a new stream
+  sDrop :: forall a.Int -> s m a -> s m a
+  -- ^ drop n items from the head of the stream
+  sTake :: forall a.Int -> s m a -> s m a
+  -- ^ take first n elemens of the stream and drop the rest
+  sFolder :: forall x b. (x -> b -> x) -> x -> s m b -> m x
+  -- ^ fold the stream using the given step function and starting value
+  sBuildFold :: forall x a b.(x -> a -> x) -> x -> (x -> b) -> FoldType s m a b
+  -- ^ Build a fold from (pure) step, start and extract functions
+  sBuildFoldM :: forall x a b.(x -> a -> m x) -> m x -> (x -> m b) -> FoldType s m a b
+  -- ^ Build a fold from (monadic) step, start and extract functions
+  sMapFoldM :: forall a b c. (b -> m c) -> FoldType s m a b -> FoldType s m a c
+  -- ^ map the output of a fold
+  sLMapFoldM :: forall a b c. (c -> m a) -> FoldType s m a b -> FoldType s m c b
+  -- ^ map the output of a fold
+  sFoldMaybe :: forall a b. FoldType s m a b -> FoldType s m (Maybe a) b
+  -- ^ fold only over Justs
+  sFold :: forall a b.FoldType s m a b -> s m a -> m b
+  -- ^ run a fold on a stream
+  sToList :: forall x. s m x -> m [x]
+  -- ^ stream to (lazy) list
+  sFromFoldable :: forall f a.Foldable f => f a -> s m a
+  -- ^ build a stream of @a@ from a foldable of @a@
+
+class (StreamFunctions s (IOSafe s m), MonadThrow (IOSafe s m), PrimMonad (IOSafe s m)) => StreamFunctionsIO (s :: (Type -> Type) -> Type -> Type) m where
+  type IOSafe s m :: Type -> Type
+  runSafe :: forall a.IOSafe s m a ->  m a
+  -- ^ Unwrap computation fram a resource management monad layer, if present.
+  sReadTextLines :: FilePath -> s (IOSafe s m) Text
+  -- ^ create a stream of lines of text by reading the given file
+  sTokenized :: Separator -> QuotingMode -> FilePath -> s (IOSafe s m) [Text]
+  -- ^ read lines of text, split them by a separator, handle quotation.
+{-
+  sTokenizedRaw :: Separator -> FilePath -> s (IOSafe s m) [Text]
+  -- ^ read lines of text, split them by a separator
+-}
+  sReadScanMAndFold :: forall x b.FilePath -> (x -> Text -> (IOSafe s m) x) -> (IOSafe s m) x -> FoldType s (IOSafe s m) x b -> (IOSafe s m) b
+  sWriteTextLines :: FilePath -> s (IOSafe s m) Text -> m ()
+    -- ^ streamly version handles invalid characters
+
+
+
+
+
+{-
+data StreamFunctions (s :: (Type -> Type) -> Type -> Type) (m :: Type -> Type) = StreamFunctions
+  { sThrowIfEmpty :: forall x. s m x -> m ()
+    -- ^ throw an exception if the stream is empty
+  , sCons :: forall a. a -> s m a -> s m a
+  -- ^ add an element to the head of a stream
+  , sUncons :: forall a . s m a -> m (Maybe (a, s m a))
+    -- ^ split a stream into it's head and tail, returning @m Nothing@ if the stream was empty
+  , sHead :: forall a . s m a -> m (Maybe a)
+    -- ^ return the first item of a (possibly empty) stream. @m Nothing@ if the stream was empty
+  , sMap :: forall x y. (x -> y) -> s m x -> s m y
+    -- ^ map each element of the stream using the given function
+  , sMapMaybe :: forall x y. (x -> Maybe y) -> s m x -> s m y
+    -- ^ map each element of the stream using the given function
+  , sScanM :: forall a x.(x -> a -> m x) -> m x -> s m a -> s m x
+    -- ^ map each item along with a running state to produce a new stream
+  , sDrop :: forall a.Int -> s m a -> s m a
+    -- ^ drop n items from the head of the stream
+  , sTake :: forall a.Int -> s m a -> s m a
+  -- ^ take first n elemens of the stream and drop the rest
+  , sFolder :: forall x b. (x -> b -> x) -> x -> s m b -> m x
+    -- ^ fold the stream using the given step function and starting value
+  , sBuildFold :: forall x a b.(x -> a -> x) -> x -> (x -> b) -> FoldType s m a b
+    -- ^ Build a fold from (pure) step, start and extract functions
+  , sBuildFoldM :: forall x a b.(x -> a -> m x) -> m x -> (x -> m b) -> FoldType s m a b
+    -- ^ Build a fold from (monadic) step, start and extract functions
+  , sMapFold :: forall a b c. (b -> m c) -> FoldType s m a b -> FoldType s m a c
+    -- ^ map the output of a fold
+  , sFold :: forall a b.FoldType s m a b -> s m a -> m b
+  -- ^ run a fold on a stream
+  , sToList :: forall x. s m x -> m [x]
+  -- ^ stream to (lazy) list
+  , sFromFoldable :: forall f a.Foldable f => f a -> s m a
+    -- ^ build a stream of @a@ from a foldable of @a@
+  }
+
+data StreamFunctionsIO (s :: (Type -> Type) -> Type -> Type) (m :: Type -> Type) = StreamFunctionsIO
+  {
+    sReadTextLines :: FilePath -> s m Text
+    -- ^ create a stream of lines of text by reading the given file
+  , sWriteTextLines :: FilePath -> s m Text -> m ()
+    -- ^ streamly version handles invalid characters
+  }
+
+data StreamFunctionsWithIO (s :: (Type -> Type) -> Type -> Type) (m :: Type -> Type) = StreamFunctionsWithIO
+  {
+    streamFunctions :: StreamFunctions s m
+  , streamFunctionsIO :: StreamFunctionsIO s m
+  }
+-}
diff --git a/src/Frames/Streamly/Streaming/Common.hs b/src/Frames/Streamly/Streaming/Common.hs
new file mode 100644
--- /dev/null
+++ b/src/Frames/Streamly/Streaming/Common.hs
@@ -0,0 +1,88 @@
+{-# LANGUAGE DeriveLift #-}
+
+module Frames.Streamly.Streaming.Common where
+
+
+import  Language.Haskell.TH.Syntax (Lift)
+import qualified Data.Text as T
+
+data Separator = CharSeparator Char | TextSeparator Text deriving (Show, Eq, Ord, Lift)
+
+defaultSep :: Separator
+defaultSep = CharSeparator ','
+{-# INLINEABLE defaultSep #-}
+
+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)
+
+defaultQuotingMode :: QuotingMode
+defaultQuotingMode = RFC4180Quoting '\"'
+{-# INLINEABLE defaultQuotingMode #-}
+
+separatorToText :: Separator -> Text
+separatorToText (CharSeparator c) = T.singleton c
+separatorToText (TextSeparator t) = t
+{-# INLINEABLE separatorToText #-}
+
+textToSeparator :: Text -> Separator
+textToSeparator t = if T.length t == 1 then CharSeparator (T.head t) else TextSeparator t
+{-# INLINEABLE textToSeparator #-}
+
+handleQuoting :: Separator -> QuotingMode -> [Text] -> [Text]
+handleQuoting sep quoting = case quoting of
+  NoQuoting-> id
+  RFC4180Quoting quote -> reassembleRFC4180QuotedParts (separatorToText sep) quote
+{-# INLINEABLE handleQuoting #-}
+
+-- | Helper to split a 'T.Text' on commas and strip leading and
+-- trailing whitespace from each resulting chunk.
+tokenizeRow :: Separator -> QuotingMode -> T.Text -> [T.Text]
+tokenizeRow sep quoting = handleQuoting sep quoting . splitRow sep
+{-# INLINEABLE tokenizeRow #-}
+
+splitRow :: Separator -> T.Text -> [T.Text]
+splitRow sep = case sep of
+  CharSeparator c -> T.split (== c)
+  TextSeparator t -> T.splitOn t
+{-# INLINEABLE splitRow #-}
+
+-- | Post processing applied to a list of tokens split by the
+-- separator which should have quoted sections reassembeld
+reassembleRFC4180QuotedParts :: Text -> Char -> [T.Text] -> [T.Text]
+reassembleRFC4180QuotedParts sep quoteChar = go
+  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
+        {-# INLINE prefixQuoted #-}
+        prefixQuoted t =
+          T.head t == quoteChar &&
+          T.length (T.takeWhile (== quoteChar) t) `rem` 2 == 1
+        {-# INLINE suffixQuoted #-}
+        suffixQuoted t =
+          quoteText `T.isSuffixOf` t &&
+          T.length (T.takeWhileEnd (== quoteChar) t) `rem` 2 == 1
+        {-# INLINE quoteText #-}
+        quoteText = T.singleton quoteChar
+        {-# INLINE unescape #-}
+        unescape :: T.Text -> T.Text
+        unescape = T.replace q2 quoteText
+          where q2 = quoteText <> quoteText
+{-# INLINEABLE reassembleRFC4180QuotedParts #-}
diff --git a/src/Frames/Streamly/Streaming/Pipes.hs b/src/Frames/Streamly/Streaming/Pipes.hs
new file mode 100644
--- /dev/null
+++ b/src/Frames/Streamly/Streaming/Pipes.hs
@@ -0,0 +1,158 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE UndecidableInstances #-}
+module Frames.Streamly.Streaming.Pipes
+  (
+    PipeStream(..)
+    -- * re-exports for MonadSafe
+  , MonadSafe
+  , SafeT
+  , runSafeT
+  ) where
+
+import Frames.Streamly.Streaming.Class
+import qualified Frames.Streamly.Streaming.Common as Common
+import Frames.Streamly.Internal.CSV (FramesCSVException(..))
+
+import qualified Pipes
+import Pipes ((>->))
+import qualified Pipes.Prelude as Pipes
+import qualified Pipes.Safe.Prelude as PSafe
+import qualified Pipes.Safe as PSafe
+import Pipes.Safe (MonadSafe, SafeT, runSafeT)
+import qualified Pipes.Prelude.Text as PText
+import qualified System.IO as IO
+import qualified Control.Foldl as Foldl
+import           Control.Monad.Catch                     ( MonadThrow(..))
+import Data.Maybe (fromJust)
+import qualified Data.ByteString.Lazy as BL
+--import Control.Monad.IO.Class (MonadIO(..))
+import qualified Data.Text.Encoding as Text
+import Data.Word8 (_lf)
+
+newtype PipeStream m a = PipeStream { producer :: Pipes.Producer a m () }
+
+instance Monad m => StreamFunctions PipeStream m where
+  type FoldType PipeStream = Foldl.FoldM
+  sThrowIfEmpty = pipesThrowIfEmpty . producer
+  sLength = Pipes.length . producer
+  sCons a s = PipeStream $ Pipes.yield a >> producer s
+  sUncons = pipeStreamUncons
+  sHead = Pipes.head . producer
+  sMap f s = PipeStream $ producer s >-> Pipes.map f
+  sMapMaybe f s = PipeStream $  producer s >-> Pipes.mapMaybe f
+  sScanM step start s = PipeStream $ producer s >-> Pipes.scanM step start return
+  sDrop n s = PipeStream $ producer s >-> Pipes.drop n
+  sTake n s = PipeStream $ producer s >-> Pipes.take n
+  sFolder step start = pipesFolder step start . producer
+  sBuildFold = pipesBuildFold
+  sBuildFoldM = pipesBuildFoldM
+  sMapFoldM = pipesPostMapM
+  sLMapFoldM = Foldl.premapM
+  sFoldMaybe = pipesFoldMaybe
+  sFold fld  = Foldl.impurely Pipes.foldM fld . producer
+  sToList = Pipes.toListM . producer -- this might be bad (not lazy) compared to streamly
+  sFromFoldable = PipeStream . pipesFromFoldable
+
+  {-# INLINEABLE sThrowIfEmpty #-}
+  {-# INLINEABLE sLength #-}
+  {-# INLINEABLE sCons #-}
+  {-# INLINEABLE sUncons #-}
+  {-# INLINEABLE sHead #-}
+  {-# INLINEABLE sMap #-}
+  {-# INLINEABLE sMapMaybe #-}
+  {-# INLINEABLE sScanM #-}
+  {-# INLINEABLE sDrop #-}
+  {-# INLINEABLE sTake #-}
+  {-# INLINEABLE sFolder #-}
+  {-# INLINEABLE sBuildFold #-}
+  {-# INLINEABLE sBuildFoldM #-}
+  {-# INLINEABLE sMapFoldM #-}
+  {-# INLINEABLE sLMapFoldM #-}
+  {-# INLINEABLE sFoldMaybe #-}
+  {-# INLINEABLE sFold #-}
+  {-# INLINEABLE sToList #-}
+  {-# INLINEABLE sFromFoldable #-}
+
+instance (Monad m, MonadThrow m, PSafe.MonadMask m, MonadIO m, Foldl.PrimMonad (PSafe.SafeT m)) => StreamFunctionsIO PipeStream m where
+  type IOSafe PipeStream m = PSafe.SafeT m
+  runSafe = PSafe.runSafeT
+  sReadTextLines fp = PipeStream $ PSafe.withFile fp IO.ReadMode unfoldViaBS
+  sTokenized sep qm = sMap (Common.tokenizeRow sep qm) . sReadTextLines
+  sReadScanMAndFold = pipestreamReadScanMAndFold
+  sWriteTextLines fp s = PSafe.runSafeT $ Pipes.runEffect $ (producer s) Pipes.>-> PText.writeFileLn fp
+
+  {-# INLINEABLE runSafe #-}
+  {-# INLINEABLE sReadTextLines #-}
+  {-# INLINEABLE sTokenized #-}
+  {-# INLINEABLE sReadScanMAndFold #-}
+  {-# INLINEABLE sWriteTextLines #-}
+
+
+pipestreamReadScanMAndFold :: MonadSafe m => FilePath -> (x -> Text -> m x) -> m x -> Foldl.FoldM m x b -> m b
+pipestreamReadScanMAndFold fp scanStep scanStart fld = Foldl.impurely Pipes.foldM fld $ PText.readFileLn fp >-> Pipes.scanM scanStep scanStart return
+{-# INLINE pipestreamReadScanMAndFold #-}
+
+pipesFoldMaybe :: Monad m => Foldl.FoldM m a b -> Foldl.FoldM m (Maybe a) b
+pipesFoldMaybe = Foldl.prefilterM (return . isJust) . Foldl.premapM (return . fromJust)
+
+
+pipesPostMapM :: Monad m => (b -> m c) -> Foldl.FoldM m a b -> Foldl.FoldM m a c
+pipesPostMapM f (Foldl.FoldM step begin done) = Foldl.FoldM step begin done'
+  where done' x = done x >>= f
+{-# INLINABLE pipesPostMapM #-}
+
+pipesBuildFold :: Monad m => (x -> a -> x) -> x -> (x -> b) -> Foldl.FoldM m a b
+pipesBuildFold step start extract = Foldl.generalize $ Foldl.Fold step start extract
+{-# INLINEABLE pipesBuildFold #-}
+
+pipesBuildFoldM :: (x -> a -> m x) -> m x -> (x -> m b) -> Foldl.FoldM m a b
+pipesBuildFoldM = Foldl.FoldM
+{-# INLINEABLE pipesBuildFoldM #-}
+
+pipesThrowIfEmpty :: MonadThrow m => Pipes.Producer a m () -> m ()
+pipesThrowIfEmpty s = Pipes.null s >>= \b -> if b then throwM EmptyStreamException else return ()
+{-# INLINEABLE pipesThrowIfEmpty #-}
+
+pipesFolder :: Monad m => (x -> b -> x) -> x -> Pipes.Producer b m () -> m x
+pipesFolder step start = Pipes.fold step start id
+{-# INLINE pipesFolder #-}
+
+pipesFromFoldable :: (Functor m, Foldable f) => f a -> Pipes.Producer a m ()
+pipesFromFoldable = Pipes.each
+{-# INLINEABLE pipesFromFoldable #-}
+
+pipeStreamUncons :: Monad m => PipeStream m a -> m (Maybe (a, PipeStream m a))
+pipeStreamUncons p = do
+  pUncons <- Pipes.next (producer p)
+  case pUncons of
+    Left () -> return Nothing
+    Right (a, s) -> return $ Just (a, PipeStream s)
+{-# INLINABLE pipeStreamUncons #-}
+
+unfoldViaBS' :: Monad m => BL.ByteString -> Pipes.Producer BL.ByteString m ()
+unfoldViaBS' = Pipes.unfoldr inner
+  where
+    {-# INLINE inner #-}
+    inner input'
+      | BL.null input' = pure $ Left ()
+      | otherwise =
+          case BL.elemIndex _lf input' of
+            Nothing -> pure $ Right (input', BL.empty)
+            Just i ->
+              let (prefix, suffix) = BL.splitAt i input'
+              in pure $ Right (prefix, BL.drop 1 suffix)
+{-# INLINE unfoldViaBS' #-}
+
+unfoldViaBS :: MonadIO m => IO.Handle -> Pipes.Producer Text m ()
+unfoldViaBS h = do
+  lbs <- Pipes.lift $ liftIO $ BL.hGetContents h
+  unfoldViaBS' lbs >-> Pipes.map (Text.decodeUtf8 . BL.toStrict)
+{-# INLINEABLE unfoldViaBS #-}
diff --git a/src/Frames/Streamly/Streaming/Streamly.hs b/src/Frames/Streamly/Streaming/Streamly.hs
new file mode 100644
--- /dev/null
+++ b/src/Frames/Streamly/Streaming/Streamly.hs
@@ -0,0 +1,537 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE InstanceSigs #-}
+module Frames.Streamly.Streaming.Streamly
+  (
+    StreamlyStream(..)
+    -- * re-exports
+#if MIN_VERSION_streamly(0,9,0)
+  , Stream
+#else
+  , SerialT
+  , IsStream
+#endif
+  )
+where
+
+import Frames.Streamly.Streaming.Class
+import qualified Frames.Streamly.Streaming.Common as Common
+
+import Frames.Streamly.Internal.CSV (FramesCSVException(..))
+import           Control.Monad.Catch                     ( MonadThrow(..), MonadCatch)
+import Control.Foldl (PrimMonad)
+import Control.Exception (try)
+import qualified Control.Monad.Trans.Control as MC
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as Text
+import Data.Word8 (_lf)
+
+#if MIN_VERSION_streamly(0,9,0)
+import qualified Streamly.Data.Fold              as Streamly.Fold
+import Streamly.Data.Stream (Stream)
+import qualified Streamly.Data.Stream as Streamly
+import qualified Streamly.Data.StreamK as StreamK
+import qualified Streamly.FileSystem.File      as Streamly.File
+import qualified Streamly.Unicode.Stream           as Unicode
+import qualified Streamly.Data.Unfold          as Streamly.Unfold
+import qualified Streamly.FileSystem.Handle    as Streamly.Handle
+import qualified Streamly.Internal.Data.Stream.Chunked as Array.Stream
+import qualified Streamly.External.ByteString as Streamly.BS
+#elif MIN_VERSION_streamly(0,8,0)
+import qualified Streamly.Prelude                       as Streamly
+import qualified Streamly.Data.Fold                     as Streamly.Fold
+import qualified Streamly.Internal.FileSystem.File      as Streamly.File
+import qualified Streamly.Internal.FileSystem.Handle    as Streamly.Handle
+import qualified Streamly.Internal.Data.Unfold          as Streamly.Unfold
+import qualified Streamly.External.ByteString as Streamly.BS
+import Streamly.Prelude                       (IsStream, SerialT)
+import qualified Streamly.Unicode.Stream           as Unicode
+import qualified Streamly.Internal.Data.Array.Stream.Foreign as Array.Stream
+import qualified Streamly.Internal.Data.Stream.StreamD.Generate as StreamD
+import qualified Streamly.Internal.Data.Stream.StreamD.Type as StreamD
+import qualified Streamly.Internal.Data.Stream.StreamD.Transform as StreamD
+#else
+
+import qualified Streamly                               as Streamly
+import           Streamly                                ( IsStream, SerialT )
+import qualified Streamly.Data.Unicode.Stream           as Streamly.Unicode
+import qualified Streamly.Internal.Data.Fold as Streamly.Fold
+#endif
+
+import qualified Data.Text.IO as Text
+import qualified System.IO as IO
+import GHC.IO.Exception (IOException)
+
+newtype StreamlyStream (t ::  (Type -> Type) -> Type -> Type) m a = StreamlyStream { stream :: t m a }
+
+#if  MIN_VERSION_streamly(0,9,0)
+instance (Monad m) => StreamFunctions (StreamlyStream Streamly.Stream) m where
+  type FoldType (StreamlyStream Streamly.Stream) = Streamly.Fold.Fold
+  sThrowIfEmpty = streamlyThrowIfEmpty . stream
+  sLength = Streamly.fold Streamly.Fold.length . stream
+  sCons a = StreamlyStream . Streamly.cons a . stream
+  sUncons = streamlyStreamUncons
+  sHead = Streamly.fold Streamly.Fold.one . stream
+  sMap f = StreamlyStream . fmap f . stream
+  sMapMaybe f = StreamlyStream . Streamly.mapMaybe f . stream
+  sScanM step start = StreamlyStream . Streamly.scan (Streamly.Fold.foldlM' step start) . stream
+  sDrop :: Monad m => Int -> StreamlyStream Streamly.Stream m a -> StreamlyStream Streamly.Stream m a
+  sDrop n = StreamlyStream . Streamly.drop n . stream
+  sTake n = StreamlyStream . Streamly.take n . stream
+  sFolder step start = streamlyFolder step start . stream
+  sBuildFold = streamlyBuildFold
+  sBuildFoldM = streamlyBuildFoldM
+  sMapFoldM = Streamly.Fold.rmapM
+  sLMapFoldM = Streamly.Fold.lmapM
+  sFoldMaybe = Streamly.Fold.catMaybes
+  sFold fld  = Streamly.fold fld . stream
+  sToList = Streamly.toList . stream -- this might be bad (not lazy) compared to streamly
+  sFromFoldable = StreamlyStream . StreamK.toStream . StreamK.fromFoldable
+#else
+instance (IsStream t, Monad m) => StreamFunctions (StreamlyStream t) m where
+  type FoldType (StreamlyStream t) = Streamly.Fold.Fold
+  sThrowIfEmpty = streamlyThrowIfEmpty . stream
+  sLength = Streamly.length . Streamly.adapt . stream
+  sCons a = StreamlyStream . Streamly.cons a . stream
+  sUncons = streamlyStreamUncons
+  sHead = Streamly.head . Streamly.adapt . stream
+  sMap f = StreamlyStream . Streamly.map f . stream
+  sMapMaybe f = StreamlyStream . Streamly.mapMaybe f . stream
+  sScanM step start = StreamlyStream . Streamly.scanlM' step start . stream
+  sDrop n = StreamlyStream . Streamly.drop n . stream
+  sTake n = StreamlyStream . Streamly.take n . stream
+  sFolder step start = streamlyFolder step start . stream
+  sBuildFold = streamlyBuildFold
+  sBuildFoldM = streamlyBuildFoldM
+  sMapFoldM = Streamly.Fold.rmapM
+  sLMapFoldM = Streamly.Fold.lmapM
+  sFoldMaybe = Streamly.Fold.catMaybes
+  sFold fld  = Streamly.fold fld . Streamly.adapt . stream
+  sToList = Streamly.toList . Streamly.adapt . stream -- this might be bad (not lazy) compared to streamly
+  sFromFoldable = StreamlyStream . Streamly.fromFoldable
+#endif
+  {-# INLINEABLE sThrowIfEmpty #-}
+  {-# INLINEABLE sLength #-}
+  {-# INLINEABLE sCons #-}
+  {-# INLINEABLE sUncons #-}
+  {-# INLINEABLE sHead #-}
+  {-# INLINEABLE sMap #-}
+  {-# INLINEABLE sMapMaybe #-}
+  {-# INLINEABLE sScanM #-}
+  {-# INLINEABLE sDrop #-}
+  {-# INLINEABLE sTake #-}
+  {-# INLINEABLE sFolder #-}
+  {-# INLINEABLE sBuildFold #-}
+  {-# INLINEABLE sBuildFoldM #-}
+  {-# INLINEABLE sMapFoldM #-}
+  {-# INLINEABLE sLMapFoldM #-}
+  {-# INLINEABLE sFoldMaybe #-}
+  {-# INLINEABLE sFold #-}
+  {-# INLINEABLE sToList #-}
+  {-# INLINEABLE sFromFoldable #-}
+
+#if  MIN_VERSION_streamly(0,9,0)
+instance (MonadCatch m, MonadIO m, PrimMonad m, MC.MonadBaseControl IO m) => StreamFunctionsIO (StreamlyStream Streamly.Stream) m where
+  type IOSafe (StreamlyStream Streamly.Stream) m = m
+  runSafe = id
+  sReadTextLines = StreamlyStream . streamlyReadTextLines linesUsingSplitOn
+  sTokenized sep qm = StreamlyStream . streamlyReadTextLines (tokenized sep qm)
+  sReadScanMAndFold = streamlyReadScanMAndFold
+  sWriteTextLines fp = streamlyWriteTextLines fp . stream
+#else
+instance (IsStream t, Streamly.MonadAsync m, MonadCatch m, PrimMonad m) => StreamFunctionsIO (StreamlyStream t) m where
+  type IOSafe (StreamlyStream t) m = m
+  runSafe = id
+  sReadTextLines = StreamlyStream . streamlyReadTextLines linesUsingSplitOn
+  sTokenized sep qm = StreamlyStream . streamlyReadTextLines (tokenized sep qm)
+  sReadScanMAndFold = streamlyReadScanMAndFold
+  sWriteTextLines fp = streamlyWriteTextLines fp . stream
+#endif
+  {-# INLINEABLE runSafe #-}
+  {-# INLINEABLE sReadTextLines #-}
+  {-# INLINEABLE sTokenized #-}
+  {-# INLINEABLE sReadScanMAndFold #-}
+  {-# INLINEABLE sWriteTextLines #-}
+
+#if  MIN_VERSION_streamly(0,9,0)
+streamlyStreamUncons :: Monad m => StreamlyStream Streamly.Stream m a -> m (Maybe (a, StreamlyStream Streamly.Stream m a))
+streamlyStreamUncons s = do
+  unc <- Streamly.uncons (stream s)
+  case unc of
+    Nothing -> return Nothing
+    Just (a, s') -> return $ Just (a, StreamlyStream s')
+#else
+streamlyStreamUncons :: (IsStream t, Monad m) => StreamlyStream t m a -> m (Maybe (a, StreamlyStream t m a))
+streamlyStreamUncons s = do
+  unc <- Streamly.uncons (Streamly.adapt $ stream s)
+  case unc of
+    Nothing -> return Nothing
+    Just (a, s') -> return $ Just (a, StreamlyStream s')
+#endif
+{-# INLINABLE streamlyStreamUncons #-}
+
+streamlyBuildFold :: Monad m => (x -> a -> x) -> x -> (x -> b) -> Streamly.Fold.Fold m a b
+#if MIN_VERSION_streamly(0,8,0)
+streamlyBuildFold step start extract = fmap extract $ Streamly.Fold.foldl' step start
+#else
+streamlyBuildFold step start extract = Streamly.Fold.mkPure step start extract
+#endif
+
+streamlyBuildFoldM :: Monad m => (x -> a -> m x) -> m x -> (x -> m b) -> Streamly.Fold.Fold m a b
+#if MIN_VERSION_streamly(0,8,0)
+streamlyBuildFoldM step start extract = Streamly.Fold.rmapM  extract $ Streamly.Fold.foldlM' step start
+#else
+streamlyBuildFoldM step start extract = Streamly.Fold.mkFold step start extract
+#endif
+
+#if  MIN_VERSION_streamly(0,9,0)
+streamlyWriteTextLines :: (MonadCatch m, MonadIO m) => FilePath -> Streamly.Stream m Text -> m ()
+#else
+streamlyWriteTextLines :: (IsStream s, Streamly.MonadAsync m, MonadCatch m) => FilePath -> s m Text -> m ()
+#endif
+streamlyWriteTextLines fp s = do
+#if MIN_VERSION_streamly(0,8,0)
+  let unfoldMany = Streamly.unfoldMany
+#else
+  let unfoldMany = Streamly.concatUnfold
+#endif
+  Streamly.fold (Streamly.File.write fp)
+    $ Unicode.encodeUtf8
+#if MIN_VERSION_streamly(0,9,0)
+    $ unfoldMany Streamly.Unfold.fromList
+    $ fmap T.unpack
+#else
+    $ Streamly.adapt
+    $ unfoldMany Streamly.Unfold.fromList
+    $ Streamly.map T.unpack
+#endif
+    $ Streamly.intersperse "\n" s
+{-# INLINEABLE streamlyWriteTextLines #-}
+
+-- Use Text to read a line at a time
+streamlyUnfoldTextLn :: MonadIO m => Streamly.Unfold.Unfold m IO.Handle Text
+streamlyUnfoldTextLn = Streamly.Unfold.unfoldrM f where
+  getOne :: IO.Handle -> IO (Either IOException Text)
+  getOne h = try (Text.hGetLine h)
+  f h = do
+    tE <- liftIO $ getOne h
+    case tE of
+      Left _ -> return Nothing
+      Right t -> return $ Just (t, h)
+{-# INLINEABLE streamlyUnfoldTextLn #-}
+
+#if MIN_VERSION_streamly(0,9,0)
+streamlyReadTextLines :: (MonadCatch m, MonadIO m)
+                      => (IO.Handle -> Streamly.Stream m a) -> FilePath -> Streamly.Stream m a
+streamlyReadTextLines f fp = Streamly.bracketIO (liftIO $ IO.openFile fp IO.ReadMode) (liftIO . IO.hClose) f
+#else
+streamlyReadTextLines :: (Streamly.IsStream t, Streamly.MonadAsync m, MonadCatch m)
+                      => (IO.Handle -> t m a) -> FilePath -> t m a
+streamlyReadTextLines f fp = Streamly.bracket (liftIO $ IO.openFile fp IO.ReadMode) (liftIO . IO.hClose) f
+#endif
+{-# INLINEABLE streamlyReadTextLines #-}
+
+withFileLifted :: MC.MonadBaseControl IO m => FilePath -> IOMode -> (Handle -> m a) -> m a
+withFileLifted file mode action = MC.liftBaseWith (\runInBase -> withFile file mode (runInBase . action)) >>= MC.restoreM
+{-# INLINEABLE withFileLifted #-}
+
+#if MIN_VERSION_streamly(0,9,0)
+streamlyReadScanMAndFold :: (MC.MonadBaseControl IO m, MonadIO m) => FilePath -> (x -> Text -> m x) -> m x -> Streamly.Fold.Fold m x b -> m b
+streamlyReadScanMAndFold fp scanStep scanStart fld = withFileLifted fp IO.ReadMode
+  $ Streamly.fold fld . Streamly.scan (Streamly.Fold.foldlM' scanStep scanStart) . Streamly.unfold streamlyUnfoldTextLn
+#else
+streamlyReadScanMAndFold :: Streamly.MonadAsync m => FilePath -> (x -> Text -> m x) -> m x -> Streamly.Fold.Fold m x b -> m b
+streamlyReadScanMAndFold fp scanStep scanStart fld = withFileLifted fp IO.ReadMode
+  $ StreamD.fold fld . StreamD.scanlM' scanStep scanStart . StreamD.unfold streamlyUnfoldTextLn
+#endif
+{-# INLINEABLE streamlyReadScanMAndFold #-}
+
+#if MIN_VERSION_streamly(0,9,0)
+streamlyThrowIfEmpty :: (MonadThrow m) => Streamly.Stream m a -> m ()
+streamlyThrowIfEmpty s = Streamly.fold Streamly.Fold.null s >>= flip when (throwM EmptyStreamException)
+#else
+streamlyThrowIfEmpty :: (IsStream t, MonadThrow m) => t m a -> m ()
+streamlyThrowIfEmpty s = Streamly.null (Streamly.adapt s) >>= flip when (throwM EmptyStreamException)
+#endif
+{-# INLINEABLE streamlyThrowIfEmpty #-}
+
+#if MIN_VERSION_streamly(0,9,0)
+streamlyFolder :: Monad m => (x -> a -> x) -> x -> Streamly.Stream m a -> m x
+streamlyFolder step start = Streamly.fold (streamlyBuildFold step start id)
+#else
+streamlyFolder :: (IsStream t, Monad m) => (x -> a -> x) -> x -> t m a -> m x
+streamlyFolder step start = Streamly.fold (streamlyBuildFold step start id) . Streamly.adapt
+#endif
+{-# INLINABLE streamlyFolder #-}
+
+#if MIN_VERSION_streamly(0,9,0)
+linesUsingSplitOn :: MonadIO m => IO.Handle -> Streamly.Stream m Text
+linesUsingSplitOn h = Streamly.unfold Streamly.Handle.chunkReader h
+                      & Array.Stream.splitOnSuffix _lf
+                      & fmap (Text.decodeUtf8 . Streamly.BS.fromArray)
+#else
+linesUsingSplitOn :: (IsStream t, MonadIO m) => IO.Handle -> t m Text
+linesUsingSplitOn h = Streamly.unfold Streamly.Handle.readChunks h
+                      & Array.Stream.splitOnSuffix _lf
+                      & Streamly.map (Text.decodeUtf8 . Streamly.BS.fromArray)
+#endif
+{-# INLINEABLE linesUsingSplitOn #-}
+
+#if MIN_VERSION_streamly(0,9,0)
+tokenized :: MonadIO m => Common.Separator -> Common.QuotingMode -> IO.Handle -> Streamly.Stream m [Text]
+tokenized sep qm h = Streamly.unfold Streamly.Handle.chunkReader h
+                     & Array.Stream.splitOnSuffix _lf
+                     & fmap (Common.tokenizeRow sep qm . Text.decodeUtf8 . Streamly.BS.fromArray)
+#else
+tokenized :: (IsStream t, MonadIO m) => Common.Separator -> Common.QuotingMode -> IO.Handle -> t m [Text]
+tokenized sep qm h = Streamly.unfold Streamly.Handle.readChunks h
+                     & Array.Stream.splitOnSuffix _lf
+                     & Streamly.map (Common.tokenizeRow sep qm . Text.decodeUtf8 . Streamly.BS.fromArray)
+#endif
+{-# INLINEABLE tokenized #-}
+
+{-
+tokenized2 :: (IsStream t, Streamly.MonadAsync m) => Common.Separator -> Common.QuotingMode -> IO.Handle -> t m [Text]
+tokenized2 sep qm h = Streamly.unfold Streamly.Handle.readChunks h
+                     & Array.Stream.splitOnSuffix _lf
+                     & wordArraysToTextLists sep qm
+{-# INLINE tokenized2 #-}
+
+wordArraysToTextLists :: (IsStream t, Streamly.MonadAsync m) => Common.Separator -> Common.QuotingMode -> t m (Array.Array Word8) -> t m [Text]
+wordArraysToTextLists sep qm = case sep of
+  Common.TextSeparator _ ->  Streamly.map (Common.tokenizeRow sep qm . Text.decodeUtf8 . Streamly.BS.fromArray)
+  Common.CharSeparator c -> Streamly.mapM (fmap (Common.handleQuoting sep qm) . processArray2 c)
+{-# INLINE wordArraysToTextLists #-}
+
+wordArraysToTextListsRaw :: (IsStream t, Streamly.MonadAsync m) => Common.Separator -> t m (Array.Array Word8) -> t m [Text]
+wordArraysToTextListsRaw sep = case sep of
+  Common.TextSeparator _ ->  Streamly.map (Common.splitRow sep . Text.decodeUtf8 . Streamly.BS.fromArray)
+  Common.CharSeparator c -> Streamly.mapM (processArray2 c)
+{-# INLINE wordArraysToTextListsRaw #-}
+
+tokenizedRaw :: (IsStream t, MonadIO m) => Common.Separator -> IO.Handle -> t m [Text]
+tokenizedRaw sep h = Streamly.unfold Streamly.Handle.readChunks h
+                     & Array.Stream.splitOnSuffix _lf
+                     & Streamly.map (Common.splitRow sep . Text.decodeUtf8 . Streamly.BS.fromArray)
+{-# INLINE tokenizedRaw #-}
+
+tokenizedRaw3 :: forall t m.(IsStream t, Streamly.MonadAsync m) => Common.Separator -> IO.Handle -> t m [Text]
+tokenizedRaw3 sep h = Streamly.unfold Streamly.Handle.readChunks h
+                      & Array.Stream.splitOnSuffix _lf
+                      & wordArraysToTextListsRaw sep
+{-# INLINE tokenizedRaw3 #-}
+
+{-# INLINE processArray2 #-}
+processArray2 :: MonadIO m => Char ->  Array.Array Word8 -> m [Text]
+processArray2 c arr =
+  let c2w = fromIntegral . ord
+      toText arr = Text.decodeUtf8 $ Streamly.BS.fromArray arr
+  in Streamly.toList $ Streamly.map toText $ Array.Stream.splitOn (c2w c) $ Streamly.fromPure arr
+
+tokenizedRaw2 :: forall t m.(IsStream t, Streamly.MonadAsync m) => Common.Separator -> IO.Handle -> t m [Text]
+tokenizedRaw2 sep h = Streamly.unfold Streamly.Handle.readChunks h & processArrayStream sep
+{-# INLINE tokenizedRaw2 #-}
+
+{-# INLINE toTextFld #-}
+toTextFld :: MonadIO m => Streamly.Fold.Fold m Word8 Text
+toTextFld = fmap (Text.decodeUtf8With Text.lenientDecode) Streamly.BS.write
+
+{-# INLINE lineFold #-}
+lineFold :: MonadIO m => Char -> Streamly.Fold.Fold m Word8 [Text]
+lineFold c =
+  let c2w = fromIntegral . ord
+      split = Streamly.Fold.takeEndBy (== c2w c) toTextFld
+  in Streamly.Fold.many split Streamly.Fold.toList
+
+
+
+
+{-# INLINE processArray #-}
+processArray :: MonadIO m => Char -> Array.Array Word8 -> m [Text]
+processArray c arr = Streamly.unfold Array.read arr
+                     & Streamly.fold (lineFold c)
+
+{-# INLINE processArrayBaseLine #-}
+processArrayBaseLine :: Char -> Array.Array Word8 -> [Text]
+processArrayBaseLine c arr =  Common.splitRow (Common.CharSeparator c) $ Text.decodeUtf8 $ Streamly.BS.fromArray arr
+
+{-# INLINE processArrayBaseLine2 #-}
+processArrayBaseLine2 :: MonadIO m => Char -> Array.Array Word8 -> m [Text]
+processArrayBaseLine2 c arr =  Common.splitRow (Common.CharSeparator c) <$> Array.fold toTextFld arr
+
+
+{-# INLINE processArrayStream #-}
+processArrayStream :: (Streamly.MonadAsync m, IsStream t) => Common.Separator -> t m (Array.Array Word8) -> t m [Text]
+processArrayStream sep arrS =
+  let arrayToText = case sep of
+        Common.TextSeparator _ ->  Streamly.map (Common.splitRow sep . Text.decodeUtf8 . Streamly.BS.fromArray)
+        Common.CharSeparator c -> Streamly.mapM (processArray2 c)
+  in arrayToText $ Array.Stream.splitOnSuffix _lf arrS
+-}
+{-
+tokenizedLines :: (IsStream t, MonadIO m) => Word8 -> IO.Handle -> t m Text
+tokenizedLines s h = Streamly.unfold Streamly.Handle.readChunks h
+                      & Streamly.Array.splitOnSuffix _lf
+                      & Streamly.filter (not . F.null)
+                      & Streamly.map (Text.decodeUtf8 . Streamly.BS.fromArray . S)
+{-# INLINE tokenizedLines #-}
+-}
+{-
+streamlyReadTextLines' :: (Streamly.IsStream t, Streamly.MonadAsync m, MonadCatch m) => FilePath -> t m Text
+streamlyReadTextLines' = word8ToTextLines2 . streamWord8
+{-# INLINE streamlyReadTextLines' #-}
+
+streamWord8 :: (Streamly.IsStream t, Streamly.MonadAsync m, MonadCatch m) => FilePath -> t m Word8
+streamWord8 =  Streamly.File.toBytes
+{-# INLINE streamWord8 #-}
+
+word8ToTextLines2 :: (IsStream t, MonadIO m) => t m Word8 -> t m T.Text
+word8ToTextLines2 =  Streamly.map (T.pack . Streamly.Array.toList)
+                     . Streamly.Unicode.Array.lines
+                     . Streamly.Unicode.decodeUtf8
+{-# INLINE word8ToTextLines2 #-}
+
+
+-- | Convert a stream of Word8 to lines of `Text` by decoding as UTF8 and splitting on "\n"
+word8ToTextLines :: (IsStream t, MonadIO m) => t m Word8 -> t m T.Text
+word8ToTextLines =  Streamly.splitOnSuffix(=='\n') (toText <$> Streamly.Fold.toList)
+                    . Streamly.Unicode.decodeUtf8
+{-# INLINE word8ToTextLines #-}
+-}
+
+{-
+-- use streamly to read Word8 and then parse to lines
+textLinesFromHandle :: (MonadThrow m, IsStream t) => MonadIO m => IO.Handle -> t m Text
+textLinesFromHandle = Reduce.parseManyD newlineParserD . Streamly.unfold Streamly.Handle.read
+{-# INLINE textLinesFromHandle #-}
+
+
+foldWord8ToText :: Monad m => Streamly.Fold.Fold m Word8 Text
+foldWord8ToText = fmap (T.pack . fmap w2c) $ Streamly.Fold.toList
+{-# INLINE foldWord8ToText #-}
+
+foldWord8ToText2 :: Monad m => Streamly.Fold.Fold m Word8 Text
+foldWord8ToText2 = fmap TB.run $ Streamly.Fold.foldl' (\b w -> b <> TB.char (w2c w)) mempty
+{-# INLINE foldWord8ToText2 #-}
+
+
+newlineParserD :: (MonadIO m, MonadThrow m) => ParserD.Parser m Word8 Text
+newlineParserD = ParserD.takeWhile (/= _lf) foldWord8ToText2 <* next
+{-# INLINE newlineParserD #-}
+
+data PState = PState !FArray.Array Word8 [Text]
+
+arrayWordStreamToTextStream :: t m (FArray.Array Word8) -> t m Text
+arrayWordStreamToTextStream = Reduce.foldIterateM g initial where
+  g :: PState -> m (Streamly.Fold (FArray.Array Word8) PState)
+  g (PState remainder lines) = Streamly.Fold.runStep fld remainder where
+    fld :: Streamly.Fold (FArray.Array Word8) PState = Streamly.Fold.foldlM' step p0 where
+      step :: PState -> FArray.Array Word8 -> m PState
+      step (PState remainder lines) cur = do
+        newChunk <- FArray.spliceTwo remainder cur
+        (newRemainder, newLines) <- breakAll newChunk
+        return $ PState newRemainder newLines
+      p0 = PState mempty []
+  initial :: PState
+  initial = PState mempty []
+
+
+breakAll :: FArray.Array Word8 -> m (FArray.Array Word8, [FArray.Array Word8])
+breakAll a = go (a, []) where
+  go a l = do
+    ma <- FArray.breakOn _lf a
+    case ma of
+      (prefix, Just suffix) -> go (prefix, suffix : l)
+      (prefix, Nothing) -> return (prefix, l)
+{-# INLINE breakAllOn #-}
+
+next :: Monad m => ParserD.Parser m a (Maybe a)
+next = ParserD.Parser step initial extract
+  where
+  initial = pure $ ParserD.IPartial ()
+  step _ a = pure $ ParserD.Done 0 (Just a)
+  extract _ = pure Nothing
+{-# INLINE next #-}
+-}
+
+{-
+lines :: BL.ByteString -> DL.DList (BL.ByteString)
+lines = DL.unfoldr inner
+  where
+    {-# INLINE inner #-}
+    inner input'
+      | BL.null input' = Nothing
+      | otherwise =
+          case BL.elemIndex _lf input' of
+            Nothing -> Just (input', BL.empty)
+            Just i ->
+              let (prefix, suffix) = BL.splitAt i input'
+              in Just (prefix, BL.drop 1 suffix)
+{-# INLINE lines #-}
+
+unfoldViaLBS' :: Applicative m => Word8 -> Streamly.Unfold.Unfold m BL.ByteString BL.ByteString
+unfoldViaLBS' w = Streamly.Unfold.unfoldr inner
+  where
+    {-# INLINE inner #-}
+    inner input'
+      | BL.null input' = Nothing
+      | otherwise =
+          case BL.elemIndex w input' of
+            Nothing -> Just (input', BL.empty)
+            Just i ->
+              let (prefix, suffix) = BL.splitAt i input'
+              in Just (prefix, BL.drop 1 suffix)
+{-# INLINE unfoldViaLBS' #-}
+
+unfoldViaSBS' :: Applicative m => Word8 -> Streamly.Unfold.Unfold m BS.ByteString BS.ByteString
+unfoldViaSBS' w = Streamly.Unfold.unfoldr inner
+  where
+    {-# INLINE inner #-}
+    inner input'
+      | BS.null input' = Nothing
+      | otherwise =
+          case BS.elemIndex w input' of
+            Nothing -> Just (input', BS.empty)
+            Just i ->
+              let (prefix, suffix) = BS.splitAt i input'
+              in Just (prefix, BS.drop 1 suffix)
+{-# INLINE unfoldViaSBS' #-}
+
+
+unfoldViaLBS :: MonadIO m => Word8 -> Streamly.Unfold.Unfold m IO.Handle Text
+unfoldViaLBS w = fmap (Text.decodeUtf8 . BL.toStrict) $ Streamly.Unfold.lmapM (liftIO . BL.hGetContents) (unfoldViaLBS' w)
+{-# INLINE unfoldViaLBS #-}
+
+readTextLinesRaw :: (IsStream t, MonadIO m) => IO.Handle -> t m Text
+readTextLinesRaw = Streamly.foldMany lineFold . Streamly.unfoldMany read' . fileStream where
+  fileStream = Streamly.unfold Streamly.Handle.readChunks
+{-# INLINE readTextLinesRaw #-}
+
+read' :: MonadIO m => Streamly.Unfold.Unfold m (F.Array Word8) Word8
+read' = Streamly.Unfold.lmap F.unsafeThaw FM.read
+{-# INLINE [0] read' #-}
+
+lineFold :: Applicative m => Streamly.Fold.Fold m Word8 Text
+lineFold = Streamly.Fold.Fold step initial extract
+  where
+    dlToText = T.pack . fmap w2c . DL.toList
+    initial = pure $ Streamly.Fold.Partial DL.empty
+    step !s !a
+      | a == _lf = pure $ Streamly.Fold.Done $ dlToText s
+      | otherwise = pure $ Streamly.Fold.Partial (DL.snoc s a)
+    extract !s = pure $ dlToText s
+{-# INLINE [2] lineFold #-}
+
+
+-}
diff --git a/src/Frames/Streamly/TH.hs b/src/Frames/Streamly/TH.hs
new file mode 100644
--- /dev/null
+++ b/src/Frames/Streamly/TH.hs
@@ -0,0 +1,566 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE KindSignatures #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE RoleAnnotations #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeOperators #-}
+-- | 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.
+module Frames.Streamly.TH
+  (
+    -- * Declare Individual Column Types
+    declareColumn
+  , declareColumnType
+  , declarePrefixedColumn
+  , declarePrefixedColumnType
+    -- * Control Type-declarations for a table
+  , RowGen(..)
+  , Separator(..)
+  , defaultSep
+  , defaultIsMissing
+  , DefaultStream
+  , OrMissingWhen(..)
+  , setOrMissingWhen
+  , rowGen
+  , rowGenCat
+  , modifyColumnSelector
+  , modifyRowTypeNameAndColumnSelector
+  , allColumnsAsNamed
+  , noHeaderColumnsNumbered
+  , prefixColumns
+  , prefixAsNamed
+  , columnSubset
+  , renamedHeaderSubset
+  , renameSomeUsingNames
+  , renameSomeUsingPositions
+  , namedColumnNumberSubset
+  , namesGiven
+    -- * Declare all necessary types for a table
+  , tableTypes
+  , tableTypes'
+  , tableTypesText'
+  , ColumnId(..)
+    -- * Header text vs. Type name text
+  , ColTypeName(..)
+  , HeaderText(..)
+    -- * Re-exports
+  , module Frames.Streamly.OrMissing
+  )
+where
+
+import Prelude hiding (lift)
+#if MIN_VERSION_streamly(0,9,0)
+import Frames.Streamly.Streaming.Streamly (StreamlyStream, Stream)
+#else
+import Frames.Streamly.Streaming.Streamly (StreamlyStream, SerialT)
+#endif
+import Frames.Streamly.Streaming.Pipes (PipeStream)
+import Frames.Streamly.Streaming.Class
+import Frames.Streamly.Streaming.Common (Separator(..))
+import qualified Frames.Streamly.CSV as SCSV
+import Frames.Streamly.CSV (ParserOptions(..), defaultSep) -- for re-export or TH
+import Frames.Streamly.OrMissing
+import qualified Frames.Streamly.ColumnUniverse as FSCU
+import qualified Frames.Streamly.ColumnTypeable as FSCT
+
+import qualified Frames.Streamly.Internal.CSV as ICSV
+import Frames.Streamly.Internal.CSV (ColumnId(..), HeaderText(..) ,ColTypeName(..), OrMissingWhen(..))
+
+--import qualified Frames.CSV as Frames (defaultParser, ParserOptions(columnSeparator))
+
+import Data.Char (toLower)
+import qualified Data.Map as Map
+import qualified Data.Set as Set
+import qualified Data.Text as T
+import Data.Vinyl
+import Data.Vinyl.TypeLevel (RIndex)
+import Frames.Col ((:->))
+import Frames.Rec(Record)
+import Frames.Utils
+import Language.Haskell.TH hiding (Type)
+import qualified Language.Haskell.TH as TH (Type)
+import Language.Haskell.TH.Syntax hiding (Type)
+
+
+--type DefaultStream = StreamlyStream SerialT
+#if MIN_VERSION_streamly(0,9,0)
+type DefaultStream = StreamlyStream Stream
+#else
+type DefaultStream = StreamlyStream SerialT
+#endif
+
+-- | Generate a column type.
+recDec :: [TH.Type] -> TH.Type
+recDec = AppT (ConT ''Record) . go
+  where go [] = PromotedNilT
+        go (t:cs) = AppT (AppT PromotedConsT t) (go cs)
+
+-- | Declare a type synonym for a column.
+mkColSynDec :: TypeQ -> Name -> DecQ
+mkColSynDec colTypeQ colTName = tySynD colTName [] colTypeQ
+
+-- | Declare lenses for working with a column.
+mkColLensDec :: Name -> TH.Type -> T.Text -> DecsQ
+mkColLensDec colTName colTy colPName = sequenceA [tySig, val, tySig', val']
+  where nm = mkName $ T.unpack colPName
+        nm' = mkName $ T.unpack colPName <> "'"
+        -- tySig = sigD nm [t|Proxy $(conT colTName)|]
+        tySig = sigD nm [t|forall f rs.
+                           (Functor f,
+                            RElem $(conT colTName) rs (RIndex $(conT colTName) rs))
+                         => ($(pure colTy) -> f $(pure colTy))
+                         -> Record rs
+                         -> f (Record rs)
+                         |]
+        tySig' = sigD nm' [t|forall f g rs.
+                            (Functor f,
+                             RElem $(conT colTName) rs (RIndex $(conT colTName) rs))
+                          => (g $(conT colTName) -> f (g $(conT colTName)))
+                          -> Rec g rs
+                          -> f (Rec g rs)
+                          |]
+        val = valD (varP nm)
+                   (normalB [e|rlens @($(conT colTName)) . rfield |])
+                   []
+        val' = valD (varP nm')
+                    (normalB [e|rlens' @($(conT colTName))|])
+                    []
+
+lowerHead :: T.Text -> Maybe T.Text
+lowerHead = fmap aux . T.uncons
+  where aux (c,t) = T.cons (toLower c) t
+
+-- | For each column, we declare a type synonym for its type, and a
+-- Proxy value of that type.
+colDec :: T.Text -> String -> T.Text -> Bool
+       -> Either (String -> Q [Dec]) TH.Type
+       -> Q (TH.Type, [Dec])
+colDec prefix rowName colName addOrMissing colTypeGen = do
+  (colTy', extraDecs) <- either colDecsHelper (pure . (,[])) colTypeGen
+  let colTy = addOrMissingToTypeIf addOrMissing colTy'
+      colTypeQ = [t|$(litT . strTyLit $ T.unpack colName) :-> $(return colTy)|]
+  syn <- mkColSynDec colTypeQ colTName'
+  lenses <- mkColLensDec colTName' colTy colPName
+  return (ConT colTName', syn : extraDecs ++ lenses)
+  where addOrMissingToTypeIf :: Bool -> TH.Type -> TH.Type
+        addOrMissingToTypeIf b t = if b then AppT (ConT ''OrMissing) t else t
+        colTName = sanitizeTypeName (prefix <> capitalize1 colName)
+        colPName = fromMaybe "colDec impossible" (lowerHead colTName)
+        colTName' = mkName $ T.unpack colTName
+        colDecsHelper f =
+          let qualName = rowName ++ T.unpack (capitalize1 colName)
+          in (ConT (mkName qualName),) <$> f qualName
+
+-- | Splice for manually declaring a column of a given type. For
+-- example, @declareColumn "x2" ''Double@ will declare a type synonym
+-- @type X2 = "x2" :-> Double@ and a lens @x2@.
+declareColumn :: T.Text -> Name -> DecsQ
+declareColumn = flip declarePrefixedColumn T.empty
+
+-- | Splice for manually declaring a column of a given type. Works
+-- like 'declareColumn', but uses a quote for the payload type
+-- rather than a specific 'Name'. This lets you declare a column with
+-- a type like, @Maybe Int@, where @Maybe Int@ is a 'Type' but not a
+-- 'Name'. For example, with @-XOverloadedStrings@ and
+-- @-XQuasiQuotes@,
+--
+-- > declareColumnType "x2" [t|Maybe Int|]
+declareColumnType :: T.Text -> Q TH.Type -> DecsQ
+declareColumnType n = declarePrefixedColumnType n T.empty
+
+-- | Splice for manually declaring a column of a given type in which
+-- the generated type synonym's name has a prefix applied to the
+-- column name. For example, @declarePrefixedColumn "x2" "my"
+-- ''Double@ will declare a type synonym @type MyX2 = "x2" :-> Double@
+-- and a lens @myX2@.
+declarePrefixedColumn :: T.Text -> T.Text -> Name -> DecsQ
+declarePrefixedColumn colName prefix colTypeName =
+  (:) <$> mkColSynDec colTypeQ colTName'
+      <*> mkColLensDec colTName' colTy colPName
+  where prefix' = capitalize1 prefix
+        colTName = sanitizeTypeName (prefix' <> capitalize1 colName)
+        colPName = fromMaybe "colDec impossible" (lowerHead colTName)
+        colTName' = mkName $ T.unpack colTName
+        colTy = ConT colTypeName
+        colTypeQ = [t|$(litT . strTyLit $ T.unpack colName) :-> $(return colTy)|]
+
+-- | Splice for manually declaring a column of a given type. Works
+-- like 'declarePrefixedColumn', but uses a quote for the payload type
+-- rather than a specific 'Name'. This lets you declare a column with
+-- a type like, @Maybe Int@, where @Maybe Int@ is a 'Type' but not a
+-- 'Name'. For example, with @-XOverloadedStrings@ and
+-- @-XQuasiQuotes@,
+--
+-- > declarePrefixedColumnType "x2" "my" [t|Maybe Int|]
+declarePrefixedColumnType :: T.Text -> T.Text -> Q TH.Type -> DecsQ
+declarePrefixedColumnType colName prefix payloadType =
+  (:) <$> mkColSynDec colTypeQ colTName'
+      <*> (payloadType >>= \colTy -> mkColLensDec colTName' colTy colPName)
+  where prefix' = capitalize1 prefix
+        colTName = sanitizeTypeName (prefix' <> capitalize1 colName)
+        colPName = fromMaybe "colDec impossible" (lowerHead colTName)
+        colTName' = mkName $ T.unpack colTName
+        colTypeQ = [t|$(litT . strTyLit $ T.unpack colName) :-> $payloadType|]
+
+-- * Default CSV Parsing
+
+-- | Control how row and named column types are generated.
+-- The first type argument indicates the type of stream used for underlying file loading
+-- The second type argument is @Text@ or @Int@ depending how columns are indexed.
+-- The third type argument is a type-level list of the possible column types.
+data RowGen (s :: (Type -> Type) -> Type -> Type) (b :: ColumnId) (a :: [Type]) =
+  RowGen { genColumnSelector    :: ICSV.RowGenColumnSelector b
+           -- ^ Use these column names. If empty, expect a
+           -- header row in the data file to provide
+           -- column names.
+         , tablePrefix    :: String
+           -- ^ A common prefix to use for every generated
+           -- declaration.
+         , separator      :: SCSV.Separator
+           -- ^ The string that separates the columns on a
+           -- row.
+         , quotingMode :: SCSV.QuotingMode
+           -- ^ Special handling for quoted fields
+         , rowTypeName    :: String
+           -- ^ The row type that enumerates all
+           -- columns.
+         , columnParsers :: FSCU.ParseHowRec a
+           -- ^ A record field that mentions the phantom type list of
+           -- possible column types. Having this field prevents record
+           -- update syntax from losing track of the type argument.
+         , inferencePrefix :: Int
+           -- ^ Number of rows to inspect to infer a type for each
+           -- column. Defaults to 1000.
+         , isMissing :: Text -> Bool
+           -- ^ Control what text is considered missing.
+           -- Defaults to @isMissing t = null t || t == "NA"@
+         , lineReader :: SCSV.Separator -> s (IOSafe s IO) [Text]
+           -- ^ A producer of rows of ’T.Text’ values that were
+           -- separated by a 'Separator' value.
+         }
+
+defaultIsMissing :: Text -> Bool
+defaultIsMissing t = T.null t || t == "NA"
+
+-- | A default 'RowGen'. This instructs the type inference engine to
+-- get column names from the data file, use the default column
+-- separator (a comma), infer column types from the default 'Columns'
+-- set of types, and produce a row type with name @Row@.
+-- Polymorphic in the stream type
+rowGen' :: forall s. StreamFunctionsIO s IO => FilePath -> RowGen s 'ColumnByName FSCU.CommonColumns
+rowGen' fp = RowGen
+  allColumnsAsNamed \
+  ""
+  SCSV.defaultSep
+  SCSV.defaultQuotingMode
+  "Row"
+  FSCU.parseableParseHowRec
+  1000
+  defaultIsMissing
+  (\sep -> sTokenized @s @IO sep SCSV.defaultQuotingMode fp)
+{-# INLINEABLE rowGen' #-}
+
+-- | A default 'RowGen'. This instructs the type inference engine to
+-- get column names from the data file, use the default column
+-- separator (a comma), infer column types from the default 'Columns'
+-- set of types, and produce a row type with name @Row@.
+-- Default stream type.
+rowGen :: FilePath -> RowGen DefaultStream 'ColumnByName FSCU.CommonColumns
+rowGen = rowGen' @DefaultStream
+{-RowGen
+  allColumnsAsNamed \
+  ""
+  SCSV.defaultSep
+  "Row"
+  FSCU.parseableParseHowRec
+  1000
+  defaultIsMissing
+  (SCSV.streamTokenized' @DefaultStream fp SCSV.defaultSep)
+-}
+{-# INLINEABLE rowGen #-}
+
+
+-- | Like 'rowGen\'', but will also generate custom data types for
+-- 'Categorical' variables with up to 8 distinct variants.
+rowGenCat' :: forall s. StreamFunctionsIO s IO => FilePath -> RowGen s 'ColumnByName FSCU.CommonColumnsCat
+rowGenCat' fp = RowGen
+  allColumnsAsNamed
+  ""
+  SCSV.defaultSep
+  SCSV.defaultQuotingMode
+  "Row"
+  FSCU.parseableParseHowRec
+  1000
+  defaultIsMissing
+  (\sep -> sTokenized @s @IO sep SCSV.defaultQuotingMode fp)
+{-# INLINEABLE rowGenCat' #-}
+
+-- | Like 'rowGen', but will also generate custom data types for
+-- 'Categorical' variables with up to 8 distinct variants.
+rowGenCat :: FilePath -> RowGen DefaultStream 'ColumnByName FSCU.CommonColumnsCat
+rowGenCat = rowGenCat' @DefaultStream
+{-# INLINEABLE rowGenCat #-}
+
+-- | Update or replace the columnHandler in a RowGen
+modifyColumnSelector :: forall a b s x.(ICSV.RowGenColumnSelector a -> ICSV.RowGenColumnSelector b) -> RowGen s a x -> RowGen s b x
+modifyColumnSelector f rg =
+  let newColHandler :: ICSV.RowGenColumnSelector b = f (genColumnSelector rg)
+  in rg { genColumnSelector = newColHandler }
+{-# INLINEABLE modifyColumnSelector #-}
+
+-- | Update or replace the columnHandler in a RowGen
+modifyRowTypeNameAndColumnSelector :: Text -> (ICSV.RowGenColumnSelector a -> ICSV.RowGenColumnSelector b) -> RowGen s a x -> RowGen s b x
+modifyRowTypeNameAndColumnSelector newRowName f rg =
+  let newColHandler = f (genColumnSelector rg)
+  in rg { rowTypeName = T.unpack newRowName, genColumnSelector = newColHandler }
+{-# INLINEABLE modifyRowTypeNameAndColumnSelector #-}
+
+
+-- | Default Column Handler. Declare one type per column.
+-- Use the header to generate names.
+allColumnsAsNamed :: ICSV.RowGenColumnSelector 'ColumnByName
+allColumnsAsNamed = ICSV.GenUsingHeader (\x -> ICSV.Include (ICSV.ColTypeName $ ICSV.headerText x, ICSV.NeverMissing)) (const [])
+{-# INLINEABLE allColumnsAsNamed #-}
+
+-- | Helper for declaring column types from a file with no header.
+noHeaderColumnsNumbered' :: ICSV.RowGenColumnSelector 'ColumnByPosition
+noHeaderColumnsNumbered' = ICSV.GenWithoutHeader (\n -> ICSV.Include (ICSV.ColTypeName $ show n, ICSV.NeverMissing)) (const [])
+{-# INLINEABLE noHeaderColumnsNumbered' #-}
+
+-- | Use a given prefix and append the column number to generate column types for a file with no header.
+noHeaderColumnsNumbered :: Text -> ICSV.RowGenColumnSelector 'ColumnByPosition
+noHeaderColumnsNumbered prefix = prefixColumns prefix noHeaderColumnsNumbered'
+{-# INLINEABLE noHeaderColumnsNumbered #-}
+
+-- | Add a prefix to all the generated column type names.
+prefixColumns :: Text -> ICSV.RowGenColumnSelector a -> ICSV.RowGenColumnSelector a
+prefixColumns p ch = ICSV.modifyColumnSelector ch g id where
+  g f = \x -> case f x of
+    ICSV.Exclude -> ICSV.Exclude
+    ICSV.Include (t, mw) -> ICSV.Include (ICSV.ColTypeName (p <> ICSV.colTypeName t), mw)
+{-# INLINEABLE prefixColumns #-}
+
+-- | Generate all column type names from the header but add a prefix.
+prefixAsNamed :: Text -> ICSV.RowGenColumnSelector 'ColumnByName
+prefixAsNamed p = prefixColumns p allColumnsAsNamed
+{-# INLINEABLE prefixAsNamed #-}
+
+inSetButNotList :: Ord a => Set a -> [a] -> [a]
+inSetButNotList s = Set.toList . Set.difference s . Set.fromList
+{-# INLINE inSetButNotList #-}
+
+inMapKeysButNotList :: Ord a => Map a v -> [a] -> [a]
+inMapKeysButNotList m = inSetButNotList (Map.keysSet m)
+{-# INLINE inMapKeysButNotList #-}
+
+-- | Generate types for only a subset of the columns.
+-- Generated 'ParserOptions' will select the correct columns when loading.
+columnSubset :: Ord (ICSV.ColumnIdType a) => Set (ICSV.ColumnIdType a) -> ICSV.RowGenColumnSelector a -> ICSV.RowGenColumnSelector a
+columnSubset s rgch = ICSV.modifyColumnSelector rgch g h where
+  g f = \x -> if x `Set.member` s then f x else ICSV.Exclude
+  h mrF cids = mrF cids ++ inSetButNotList s cids
+{-# INLINEABLE columnSubset #-}
+
+-- | Generate Column Type Names for only the headers given in the map.  Use
+-- the names given as values in the map rather than those in the header.
+-- Generated 'ParserOptions' will select the correct columns when loading.
+renamedHeaderSubset :: Map ICSV.HeaderText ICSV.ColTypeName -> ICSV.RowGenColumnSelector 'ColumnByName
+renamedHeaderSubset renamedS = ICSV.GenUsingHeader f mrF where
+  f x = maybe ICSV.Exclude g $ Map.lookup x renamedS
+  mrF = inMapKeysButNotList renamedS
+  g x = ICSV.Include (x, ICSV.NeverMissing)
+{-# INLINEABLE renamedHeaderSubset #-}
+
+
+-- | rename some column types while leaving the rest alone.
+renameSome ::  Ord (ICSV.ColumnIdType a)
+           => Map (ICSV.ColumnIdType a) ICSV.ColTypeName --, ICSV.OrMissingWhen)
+           -> ICSV.RowGenColumnSelector a
+           -> ICSV.RowGenColumnSelector a
+renameSome m rgcs = ICSV.modifyColumnSelector rgcs g h where
+  wom = \case
+    ICSV.Exclude -> NeverMissing
+    ICSV.Include (_, x) -> x
+  g f cid = case Map.lookup cid m of
+    Nothing -> f cid
+    Just x -> ICSV.Include (x, wom $ f cid)
+  h mrF cids = mrF cids ++ inMapKeysButNotList m cids
+{-# INLINEABLE renameSome #-}
+
+-- | rename some column types while leaving the rest alone.
+renameSomeUsingNames :: Map ICSV.HeaderText ICSV.ColTypeName --, ICSV.OrMissingWhen)
+                     -> ICSV.RowGenColumnSelector 'ICSV.ColumnByName
+                     -> ICSV.RowGenColumnSelector 'ICSV.ColumnByName
+renameSomeUsingNames = renameSome
+{-# INLINEABLE renameSomeUsingNames #-}
+
+-- | rename some column types while leaving the rest alone.
+renameSomeUsingPositions :: Map Int ICSV.ColTypeName
+                         -> ICSV.RowGenColumnSelector 'ICSV.ColumnByPosition
+                         -> ICSV.RowGenColumnSelector 'ICSV.ColumnByPosition
+renameSomeUsingPositions = renameSome
+{-# INLINEABLE renameSomeUsingPositions #-}
+
+-- | Generate Column Type Names for only the given numbered Columns using
+-- names given as values in the map.  Can ignore a header row or work without one
+-- as set by the first parameter.
+-- Generated 'ParserOptions' will select the correct columns when loading.
+namedColumnNumberSubset :: Bool -> Map Int ICSV.ColTypeName -> ICSV.RowGenColumnSelector 'ColumnByPosition
+namedColumnNumberSubset hasHeader namedS =
+  if hasHeader
+    then ICSV.GenIgnoringHeader f mrF
+    else ICSV.GenWithoutHeader f mrF
+  where
+    f n = maybe ICSV.Exclude g $ Map.lookup n namedS
+    g x = ICSV.Include (x, ICSV.NeverMissing)
+    mrF = inMapKeysButNotList namedS
+{-# INLINEABLE namedColumnNumberSubset #-}
+
+-- | Use the given names to declare Column Type Names for the
+-- first N (= length of the given list of names) columns of the given file.
+-- Generated 'ParserOptions' will select the correct columns when loading.
+namesGiven :: Bool -> [ICSV.ColTypeName] -> ICSV.RowGenColumnSelector 'ColumnByPosition
+namesGiven hasHeader names = namedColumnNumberSubset hasHeader m
+  where
+    m = Map.fromList $ zip [0..] names
+{-# INLINEABLE namesGiven #-}
+
+setOrMissingWhen :: (Eq (ICSV.ColumnIdType b)) => ICSV.ColumnIdType b -> ICSV.OrMissingWhen -> RowGen s b a -> RowGen s b a
+setOrMissingWhen cid mw rg = rg { genColumnSelector = newColSelector } where
+  colSelector = genColumnSelector rg
+  f oldF x = if x /= cid
+             then oldF x
+             else case oldF x of
+                    ICSV.Exclude -> ICSV.Exclude
+                    ICSV.Include (ctn, _) -> ICSV.Include (ctn, mw)
+  newColSelector = ICSV.modifyColumnSelector colSelector f id
+{-# INLINEABLE setOrMissingWhen #-}
+
+-- -- | Generate a type for each row of a table. This will be something
+-- -- like @Record ["x" :-> a, "y" :-> b, "z" :-> c]@.
+-- tableType :: String -> FilePath -> DecsQ
+-- tableType n fp = tableType' (rowGen fp) { rowTypeName = n }
+
+-- | Like 'tableType', but additionally generates a type synonym for
+-- each column, and a proxy value of that type. If 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 :: String -> FilePath -> DecsQ
+tableTypes n fp = tableTypes' (rowGen fp) { rowTypeName = n }
+
+-- * Customized Data Set Parsing
+
+-- | Generate a type for a row of a table. This will be something like
+-- @Record ["x" :-> a, "y" :-> b, "z" :-> c]@.  Column type synonyms
+-- are /not/ generated (see 'tableTypes'').
+-- tableType' :: forall a. (ColumnTypeable a, Monoid a)
+--            => RowGen a -> DecsQ
+-- tableType' (RowGen {..}) =
+--     pure . TySynD (mkName rowTypeName) [] <$>
+--     (runIO (P.runSafeT (readColHeaders opts lineSource)) >>= recDec')
+--   where recDec' = recDec . map (second colType) :: [(T.Text, a)] -> Q Type
+--         colNames' | null columnNames = Nothing
+--                   | otherwise = Just (map T.pack columnNames)
+--         opts = ParserOptions colNames' separator (RFC4180Quoting '\"')
+--         lineSource = lineReader separator >-> P.take prefixSize
+
+-- | Tokenize the first line of a ’Streamly.SerialT’.
+colNamesP :: (Monad m, StreamFunctions s m) => s m [Text] -> m [T.Text]
+colNamesP src = fromMaybe [] <$> sHead src
+
+-- | Generate a type for a row of a table all of whose columns remain
+-- unparsed 'Text' values.
+tableTypesText' :: forall s b a.StreamFunctionsIO s IO
+                => RowGen s b a
+                -> DecsQ
+tableTypesText' RowGen {..} = do
+  firstRow <- runIO $ runSafe @s $ colNamesP $ lineReader separator
+  let (allColStates, pch) = case genColumnSelector of
+        ICSV.GenUsingHeader f _ ->
+          let allHeaders = ICSV.HeaderText <$> firstRow
+              allColStates' = f <$> allHeaders
+          in (allColStates', ICSV.colStatesAndHeadersToParseColHandler allColStates' allHeaders)
+        ICSV.GenIgnoringHeader f _ ->
+          let allHeaders = ICSV.HeaderText <$> firstRow
+              allIndexes = [0..(length allHeaders - 1)]
+              allColStates' = f <$> allIndexes
+          in (allColStates', ICSV.ParseIgnoringHeader allColStates')
+        ICSV.GenWithoutHeader f _ ->
+          let allIndexes = [0..(length firstRow - 1)]
+              allColStates' = f <$> allIndexes
+          in (allColStates', ICSV.ParseWithoutHeader allColStates')
+      colNames = ICSV.includedColTypeNames allColStates
+
+  let opts = SCSV.ParserOptions pch separator (SCSV.RFC4180Quoting '\"')
+  let colNamesT = zip (fmap ICSV.colTypeName colNames) (repeat (ConT ''T.Text))
+  (colTypes, colDecs) <- second concat . unzip
+                         <$> mapM (uncurry mkColDecs) colNamesT
+  let recTy = TySynD (mkName rowTypeName) [] (recDec colTypes)
+      optsName = case rowTypeName of
+                   [] -> error "Row type name shouldn't be empty"
+                   h:t -> mkName $ toLower h : t ++ "Parser"
+  optsTy <- sigD optsName [t|ParserOptions|]
+  optsDec <- valD (varP optsName) (normalB $ lift opts) []
+  return (recTy : optsTy : optsDec : colDecs)
+  where mkColDecs colNm colTy = do
+          let safeName = T.unpack (sanitizeTypeName colNm)
+          mColNm <- lookupTypeName (tablePrefix ++ safeName)
+          case mColNm of
+            Just n -> pure (ConT n, [])
+            Nothing -> colDec (T.pack tablePrefix) rowTypeName colNm False (Right colTy)
+
+-- | Generate a type for a row of a table. This will be something like
+-- @Record ["x" :-> a, "y" :-> b, "z" :-> c]@. Additionally generates
+-- a type synonym for each column, and a proxy value of that type. If
+-- 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 ts b s.
+               (FSCT.ColumnTypeable (FSCU.ColType ts)
+               , Show (ICSV.ColumnIdType b)
+               , StreamFunctionsIO s IO)
+            => RowGen s b ts -> DecsQ
+tableTypes' RowGen {..} = do
+  (typedCols, pch) <- runIO
+                      $ runSafe @s
+                      $ SCSV.readColHeaders columnParsers genColumnSelector lineSource :: Q ([SCSV.ColTypeInfo (FSCU.ColType ts)], ICSV.ParseColumnSelector)
+--  let colDecsFromTypedCols :: (ICSV.ColTypeName, FSCU.ColType ts)
+--      colDecsFromTypedCols
+  (colTypes, colDecs) <- (second concat . unzip)
+                         <$> mapM mkColDecs typedCols
+  let recTy = TySynD (mkName rowTypeName) [] (recDec colTypes)
+      opts = SCSV.ParserOptions pch separator (SCSV.RFC4180Quoting '\"')
+      optsName = case rowTypeName of
+                   [] -> error "Row type name shouldn't be empty"
+                   h:t -> mkName $ toLower h : t ++ "Parser"
+  optsTy <- sigD optsName [t|ParserOptions|]
+  optsDec <- valD (varP optsName) (normalB $ lift opts) []
+  return (recTy : optsTy : optsDec : colDecs)
+  where lineSource :: s (IOSafe s IO) [Text]
+        lineSource = sTake inferencePrefix $ lineReader separator
+        inferMaybe :: ICSV.OrMissingWhen -> FSCU.SomeMissing -> Bool
+        inferMaybe mw sm = case mw of
+          ICSV.NeverMissing -> False
+          ICSV.AlwaysPossible -> True
+          ICSV.IfSomeMissing -> sm == FSCU.SomeMissing
+        mkColDecs :: SCSV.ColTypeInfo (FSCU.ColType ts) -> Q (TH.Type, [Dec])
+        mkColDecs (SCSV.ColTypeInfo colNm colMW colTy) = do
+          let safeName = tablePrefix ++ (T.unpack . sanitizeTypeName . ICSV.colTypeName $ colNm) -- ??
+              colTyTH = FSCT.colType columnParsers colTy
+              isMaybe = inferMaybe colMW (FSCU.colTypeSomeMissing colTy)
+          mColNm <- lookupTypeName safeName
+          case mColNm of
+            Just n -> pure (ConT n, []) -- Column's type was already defined
+            Nothing -> colDec (T.pack tablePrefix) rowTypeName (ICSV.colTypeName colNm) isMaybe colTyTH
diff --git a/src/Frames/Streamly/Transform.hs b/src/Frames/Streamly/Transform.hs
--- a/src/Frames/Streamly/Transform.hs
+++ b/src/Frames/Streamly/Transform.hs
@@ -36,22 +36,28 @@
 where
 
 import qualified Frames.Streamly.InCore as FS
+import Frames.Streamly.Streaming.Streamly (StreamlyStream(..))
 import Prelude hiding (filter, mapMaybe)
 
-#if MIN_VERSION_streamly(0,8,0)
+#if MIN_VERSION_streamly(0,9,0)
+import qualified Streamly.Data.Stream as Streamly
+import qualified Streamly.Data.StreamK as StreamK
+import qualified Streamly.Data.Stream.Prelude as Streamly
+#elif MIN_VERSION_streamly(0,8,0)
 import Streamly.Prelude                       (IsStream)
+import qualified Streamly.Prelude                       as Streamly
 #else
+import qualified Streamly.Prelude                       as Streamly
 import qualified Streamly                               as Streamly
 import           Streamly                                ( IsStream )
 #endif
 
-import qualified Streamly.Prelude                       as Streamly
 import qualified Control.Monad.Primitive                as Prim
 import Control.Monad.ST (runST)
 import qualified Frames
-import qualified Frames.InCore                          as Frames
 
-#if MIN_VERSION_streamly(0,8,0)
+#if MIN_VERSION_streamly(0,9,0)
+#elif MIN_VERSION_streamly(0,8,0)
 fromSerial :: IsStream t => Streamly.SerialT m a -> t m a
 fromSerial = Streamly.fromSerial
 {-# INLINE fromSerial #-}
@@ -70,44 +76,90 @@
 #endif
 
 
+
+#if MIN_VERSION_streamly(0,9,0)
+  -- | Use streamly to transform a frame.
+transform ::
+  forall as bs m.
+  (
+  Prim.PrimMonad m
+  , FS.RecVec as
+  , FS.RecVec bs
+  )
+  => (Streamly.Stream m (Frames.Record as) -> Streamly.Stream m (Frames.Record bs)) -> Frames.FrameRec as -> m (Frames.FrameRec bs)
+transform f = FS.inCoreAoS . StreamlyStream . f . StreamK.toStream . StreamK.fromFoldable --Streamly.fromFoldable
+{-# INLINE transform #-}
+
+-- | Filter using streamly
+filter :: FS.RecVec as => (Frames.Record as -> Bool) -> Frames.FrameRec as -> Frames.FrameRec as
+filter f frame = runST $ transform (Streamly.filter f) frame
+{-# INLINE filter #-}
+
+-- | map using speculative streams (concurrency that preserves ordering of results).
+concurrentMapM :: (Streamly.MonadAsync m
+                  , Prim.PrimMonad m
+                  , FS.RecVec as
+                  , FS.RecVec bs
+                  ) => (Frames.Record as -> m (Frames.Record bs)) -> Frames.FrameRec as -> m (Frames.FrameRec bs)
+concurrentMapM f = transform (Streamly.parMapM (Streamly.ordered True) f)
+{-# INLINE concurrentMapM #-}
+
+-- | mapMaybe using streamly
+mapMaybe :: (FS.RecVec as
+            , FS.RecVec bs
+            ) => (Frames.Record as -> Maybe (Frames.Record bs)) -> Frames.FrameRec as -> Frames.FrameRec bs
+mapMaybe f frame = runST $ transform (Streamly.mapMaybe f) frame
+{-# INLINE mapMaybe #-}
+
+-- | mapMaybeM using speculative streams (concurrency that preserves ordering of results).
+concurrentMapMaybeM :: (Prim.PrimMonad m
+                       , Streamly.MonadAsync m
+                       , FS.RecVec as
+                       , FS.RecVec bs
+                       ) => (Frames.Record as -> m (Maybe (Frames.Record bs))) -> Frames.FrameRec as -> m (Frames.FrameRec bs)
+concurrentMapMaybeM f = transform (Streamly.catMaybes . Streamly.parMapM (Streamly.ordered True) f)
+{-# INLINE concurrentMapMaybeM #-}
+#else
 -- | Use streamly to transform a frame.
 transform ::
   forall t as bs m.
-  (IsStream t
+  (
+    IsStream t
   , Prim.PrimMonad m
-  , Frames.RecVec as
-  , Frames.RecVec bs
+  , FS.RecVec as
+  , FS.RecVec bs
   )
   => (t m (Frames.Record as) -> Streamly.SerialT m (Frames.Record bs)) -> Frames.FrameRec as -> m (Frames.FrameRec bs)
-transform f = FS.inCoreAoS . f . Streamly.fromFoldable
+transform f = FS.inCoreAoS . StreamlyStream . f . Streamly.fromFoldable
 {-# INLINE transform #-}
 
 -- | Filter using streamly
-filter :: (Frames.RecVec as) => (Frames.Record as -> Bool) -> Frames.FrameRec as -> Frames.FrameRec as
+filter :: FS.RecVec as => (Frames.Record as -> Bool) -> Frames.FrameRec as -> Frames.FrameRec as
 filter f frame = runST $ transform (fromSerial . Streamly.filter f) frame
 {-# INLINE filter #-}
 
 -- | map using speculative streams (concurrency that preserves ordering of results).
 concurrentMapM :: (Prim.PrimMonad m
                   , Streamly.MonadAsync m
-                  , Frames.RecVec as
-                  , Frames.RecVec bs
+                  , FS.RecVec as
+                  , FS.RecVec bs
                   ) => (Frames.Record as -> m (Frames.Record bs)) -> Frames.FrameRec as -> m (Frames.FrameRec bs)
 concurrentMapM f = transform (fromAhead . Streamly.mapM f)
 {-# INLINE concurrentMapM #-}
 
 -- | mapMaybe using streamly
-mapMaybe :: (Frames.RecVec as
-                      , Frames.RecVec bs
-                      ) => (Frames.Record as -> Maybe (Frames.Record bs)) -> Frames.FrameRec as -> Frames.FrameRec bs
+mapMaybe :: (FS.RecVec as
+            , FS.RecVec bs
+            ) => (Frames.Record as -> Maybe (Frames.Record bs)) -> Frames.FrameRec as -> Frames.FrameRec bs
 mapMaybe f frame = runST $ transform (fromAhead . Streamly.mapMaybe f) frame
 {-# INLINE mapMaybe #-}
 
 -- | mapMaybeM using speculative streams (concurrency that preserves ordering of results).
 concurrentMapMaybeM :: (Prim.PrimMonad m
                        , Streamly.MonadAsync m
-                       , Frames.RecVec as
-                       , Frames.RecVec bs
+                       , FS.RecVec as
+                       , FS.RecVec bs
                        ) => (Frames.Record as -> m (Maybe (Frames.Record bs))) -> Frames.FrameRec as -> m (Frames.FrameRec bs)
 concurrentMapMaybeM f = transform (fromAhead . Streamly.mapMaybeM f)
 {-# INLINE concurrentMapMaybeM #-}
+#endif
diff --git a/test/DemoPaths.hs b/test/DemoPaths.hs
new file mode 100644
--- /dev/null
+++ b/test/DemoPaths.hs
@@ -0,0 +1,62 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DataKinds #-}
+module DemoPaths where
+
+import qualified Paths_Frames_streamly as Paths
+import qualified Frames.Streamly.TH as FStreamly
+import qualified Frames.ColumnUniverse as Frames
+import qualified Data.Set as Set
+import qualified Data.Map as Map
+import qualified  Data.Text as T
+import qualified Frames.Streamly.TH as FStreamly
+
+forestFiresPath :: FilePath
+forestFiresPath = "forestFires.csv"
+
+forestFiresNoHeaderPath :: FilePath
+forestFiresNoHeaderPath = "forestFiresNoHeader.csv"
+
+forestFiresFewerColsPath :: FilePath
+forestFiresFewerColsPath = "forestFiresFewerCols.csv"
+
+
+thPath :: FilePath -> FilePath
+thPath x = "./example_data/" ++ x
+
+usePath :: FilePath -> IO FilePath
+usePath x =  fmap (\dd -> dd ++ "/" ++ x) Paths.getDataDir
+
+ffColSubsetRowGen :: FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByName Frames.CommonColumns
+ffColSubsetRowGen = FStreamly.modifyColumnSelector modSelector rowGen
+  where
+    rowTypeName = "FFColSubset"
+    rowGen = (FStreamly.rowGen (thPath forestFiresPath)) { FStreamly.rowTypeName = rowTypeName }
+    modSelector = FStreamly.columnSubset (Set.fromList $ fmap FStreamly.HeaderText ["X","Y","month","day","temp","wind"])
+
+ffRenameDayRowGen :: FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByName Frames.CommonColumns
+ffRenameDayRowGen = FStreamly.modifyRowTypeNameAndColumnSelector "FFRenameDay" modSelector ffColSubsetRowGen where
+  modSelector = FStreamly.renameSomeUsingNames (Map.fromList [(FStreamly.HeaderText "day", FStreamly.ColTypeName "DayOfWeek")])
+
+ffNoHeaderRowGen :: FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByPosition Frames.CommonColumns
+ffNoHeaderRowGen = FStreamly.modifyColumnSelector modSelector rowGen
+  where
+    rowTypeName = "FFNoHeader"
+    rowGen = (FStreamly.rowGen (thPath forestFiresNoHeaderPath)) { FStreamly.rowTypeName = rowTypeName }
+    modSelector = const $ FStreamly.noHeaderColumnsNumbered "Col"
+
+ffIgnoreHeaderRowGen :: FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByPosition  Frames.CommonColumns
+ffIgnoreHeaderRowGen = FStreamly.modifyColumnSelector modSelector rowGen
+  where
+    rowTypeName = "FFIgnoreHeader"
+    rowGen = (FStreamly.rowGen (thPath forestFiresPath)) { FStreamly.rowTypeName = rowTypeName }
+    modSelector = const $ FStreamly.namesGiven True $ fmap FStreamly.ColTypeName $ ("Col" <>) . T.pack . show <$> ([0..12] :: [Int])
+
+ffIgnoreHeaderChooseNamesRowGen :: FStreamly.RowGen FStreamly.DefaultStream 'FStreamly.ColumnByPosition  Frames.CommonColumns
+ffIgnoreHeaderChooseNamesRowGen = FStreamly.modifyColumnSelector modSelector rowGen
+  where
+    rowTypeName = "FFIgnoreHeaderChooseNames"
+    rowGen = (FStreamly.rowGen (thPath forestFiresPath)) { FStreamly.rowTypeName = rowTypeName }
+    modSelector = const
+                 $ FStreamly.namedColumnNumberSubset True
+                 $ fmap FStreamly.ColTypeName
+                 $ Map.fromList [(0, "X"), (1,"Y"), (2, "Month"), (3, "DayOfWeek"), (8, "Temp"), (10, "Wind")]
diff --git a/test/FramesStreamlySpec.hs b/test/FramesStreamlySpec.hs
new file mode 100644
--- /dev/null
+++ b/test/FramesStreamlySpec.hs
@@ -0,0 +1,92 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE QuantifiedConstraints #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeApplications #-}
+module FramesStreamlySpec where
+
+import SpecHelper
+
+import Data.Text (Text)
+import qualified DemoPaths as Paths
+import qualified Frames.Streamly.CSV as FStreamly
+import qualified Frames.Streamly.TH as FStreamly
+import qualified Frames.Streamly.InCore as FStreamly
+import qualified Frames.Streamly.Streaming.Class as Streaming
+import qualified Frames.Streamly.Streaming.Pipes as Streaming
+import qualified Frames.Streamly.Streaming.Streamly as Streaming
+import qualified Frames
+import qualified Control.Foldl as Foldl
+import Pipes.Safe()
+
+numRecs :: Foldable f => f a -> Int
+numRecs = Foldl.fold Foldl.length
+
+FStreamly.tableTypes "ForestFires" (Paths.thPath Paths.forestFiresPath)
+FStreamly.tableTypes' Paths.ffColSubsetRowGen
+FStreamly.tableTypes' Paths.ffRenameDayRowGen
+FStreamly.tableTypes' Paths.ffNoHeaderRowGen
+FStreamly.tableTypes' Paths.ffIgnoreHeaderRowGen
+FStreamly.tableTypes' Paths.ffIgnoreHeaderChooseNamesRowGen
+
+-- TODO
+-- 1. Test renameSome
+-- 2. Test different files with shared chosen columnNames
+
+emptyStreamSelector :: Selector FStreamly.FramesCSVException
+emptyStreamSelector FStreamly.EmptyStreamException = True
+emptyStreamSelector _ = False
+
+missingHeadersSelector :: Selector FStreamly.FramesCSVException
+missingHeadersSelector (FStreamly.MissingHeadersException _) = True
+missingHeadersSelector _ = False
+
+badHeaderSelector :: Selector FStreamly.FramesCSVException
+badHeaderSelector (FStreamly.BadHeaderException _) = True
+badHeaderSelector _ = False
+
+wrongNumberColumnsSelector :: Selector FStreamly.FramesCSVException
+wrongNumberColumnsSelector (FStreamly.WrongNumberColumnsException _) = True
+wrongNumberColumnsSelector _ = False
+
+spec :: Spec
+spec = do
+  let specF :: forall s.(Foldl.PrimMonad (Streaming.IOSafe s IO), Streaming.StreamFunctionsIO s IO) => String -> Spec
+      specF desc = do
+        forestFiresPath <- runIO $ Paths.usePath Paths.forestFiresPath
+        forestFiresNoHeaderPath <- runIO $ Paths.usePath Paths.forestFiresNoHeaderPath
+        forestFiresFewerColsPath <- runIO $ Paths.usePath Paths.forestFiresFewerColsPath
+
+        forestFires :: Frames.Frame ForestFires <- runIO $ Streaming.runSafe @s $ FStreamly.inCoreAoS $ FStreamly.readTableOpt @_ @s @IO forestFiresParser forestFiresPath
+        forestFiresColSubset :: Frames.Frame FFColSubset <- runIO $ Streaming.runSafe @s $ FStreamly.inCoreAoS $ FStreamly.readTableOpt @_ @s @IO fFColSubsetParser forestFiresPath
+        forestFiresFewerCols :: Frames.Frame FFColSubset <- runIO $ Streaming.runSafe @s $ FStreamly.inCoreAoS $ FStreamly.readTableOpt @_ @s @IO fFColSubsetParser forestFiresFewerColsPath
+        forestFiresRenameDay :: Frames.Frame FFRenameDay <- runIO $ Streaming.runSafe @s $ FStreamly.inCoreAoS $ FStreamly.readTableOpt @_ @s @IO fFColSubsetParser forestFiresPath
+        forestFiresNoHeader :: Frames.Frame FFNoHeader <- runIO $ Streaming.runSafe @s $ FStreamly.inCoreAoS $ FStreamly.readTableOpt @_ @s @IO fFNoHeaderParser forestFiresNoHeaderPath
+        forestFiresIgnoreHeader :: Frames.Frame FFIgnoreHeader <- runIO $ Streaming.runSafe @s $ FStreamly.inCoreAoS $ FStreamly.readTableOpt @_ @s @IO fFIgnoreHeaderParser forestFiresPath
+        forestFiresIgnoreHeaderChooseNames :: Frames.Frame FFIgnoreHeaderChooseNames <-
+          runIO $ Streaming.runSafe @s $ FStreamly.inCoreAoS $ FStreamly.readTableOpt @_ @s @IO fFIgnoreHeaderChooseNamesParser forestFiresPath
+        describe desc $ do
+          context "Can generate types and load the corresponding data a variety of ways" $ do
+            it "load frames and insure all have same number of rows (AllCols  vs. ColSubset)" $
+              numRecs forestFires == numRecs forestFiresColSubset
+            it "load frames and insure all have same number of rows (AllCols  vs. NoHeader)" $
+              numRecs forestFires == numRecs forestFiresNoHeader
+            it "load frames and insure all have same number of rows (AllCols  vs. IgnoreHeader)" $
+              numRecs forestFires == numRecs forestFiresIgnoreHeader
+            it "load frames and insure all have same number of rows (AllCols  vs. IgnoreHeaderChooseNames)" $
+              numRecs forestFires == numRecs forestFiresIgnoreHeaderChooseNames
+            it "generate types from each header and rcast to drop columns or load only that column subset.  Those should be the same" $
+              let rcasted :: Frames.FrameRec [X, Y, Month, Day, Temp, Wind] = fmap Frames.rcast forestFires
+              in rcasted == forestFiresColSubset
+            it "parse a file with different columsn as long as the required subset is present. This should be the same as the full file" $
+              forestFiresColSubset == forestFiresFewerCols
+            it "generate types from column numbers rather than headers.  Also from column numbers when there is no header.  Those should be the same." $
+              forestFiresNoHeader == forestFiresIgnoreHeader
+            it "generate named types for columns chosen by col number.  In this case, should match col subset after renaming Day to DayOfWeek" $
+              forestFiresIgnoreHeaderChooseNames == forestFiresRenameDay
+            it "throw an exception when parsing (ignoring or absent a header line) a file with the wrong number of columns" $ do
+              (Streaming.runSafe @s $ FStreamly.inCoreAoS $ FStreamly.readTableOpt @(Frames.RecordColumns ForestFires) @s @IO fFIgnoreHeaderParser forestFiresFewerColsPath) `shouldThrow` wrongNumberColumnsSelector
+  specF @(Streaming.StreamlyStream Streaming.SerialT) "Streamly backend"
+  specF @Streaming.PipeStream "Pipes backend"
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/test/SpecHelper.hs b/test/SpecHelper.hs
new file mode 100644
--- /dev/null
+++ b/test/SpecHelper.hs
@@ -0,0 +1,5 @@
+module SpecHelper (
+  module Test.Hspec
+) where
+
+import Test.Hspec
