diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,2 @@
+v 0.1.0.0
+* Initial hackage release
diff --git a/Frames-map-reduce.cabal b/Frames-map-reduce.cabal
new file mode 100644
--- /dev/null
+++ b/Frames-map-reduce.cabal
@@ -0,0 +1,54 @@
+cabal-version:       2.2
+
+name:                Frames-map-reduce
+version:             0.1.0.0
+synopsis:            Frames wrapper for map-reduce-folds and some extra folds helpers.
+description:         Frames-map-reduce provides some helpers for using the map-reduce-folds library with vinyl records and Frames.
+                     These include functions for filtering Frames, splitting records into key columns and data columns and
+                     recombining key columns with other columns after reducing.
+                     This package also provides some tools for building folds over records from folds over each column,
+                     e.g, summing multiple numerical columns into a multi-column result.
+license:             BSD-3-Clause
+license-file:        LICENSE
+author:              Adam Conner-Sax
+maintainer:          adam_conner_sax@yahoo.com
+copyright:           2019 Adam Conner-Sax
+category:            Data
+extra-source-files:  CHANGELOG.md    
+Build-type: Simple
+tested-with: GHC ==8.6.4 || ==8.6.2
+
+source-repository head
+    Type: git
+    Location: https://github.com/adamConnerSax/Frames-map-reduce                                     
+            
+library
+  ghc-options: -Wall -funbox-strict-fields
+  exposed-modules: Frames.Folds
+                 , Frames.MapReduce
+
+  build-depends: Frames               >= 0.6.1 && < 0.7,
+                 base                 >= 4.12.0 && < 4.13,
+                 containers           >= 0.6.0 && < 0.7,
+                 hashable             >= 1.2.7 && < 1.3,
+                 map-reduce-folds     >= 0.1.0.0,                 
+                 profunctors          >= 5.3 && < 5.4,
+                 vinyl                >= 0.11.0 && < 0.12,
+                 foldl                >= 1.4.5 && < 1.5,
+                 newtype              >= 0.2 && < 0.3
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+executable AddRowsByLabel
+    main-is: AddRowsByLabel.hs
+    hs-source-dirs: examples
+    ghc-options: -Wall
+    build-depends:
+                  base,
+                  foldl,                  
+                  Frames,
+                  Frames-map-reduce -any,
+                  text >= 1.2.3 && < 1.3, 
+                  vinyl,
+                  random >= 1.1 && < 1.2,                  
+    default-language:    Haskell2010
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2018, Adam Conner-Sax
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Adam Conner-Sax nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/examples/AddRowsByLabel.hs b/examples/AddRowsByLabel.hs
new file mode 100644
--- /dev/null
+++ b/examples/AddRowsByLabel.hs
@@ -0,0 +1,62 @@
+{-# LANGUAGE TypeApplications  #-}
+{-# LANGUAGE TypeOperators     #-}
+{-# LANGUAGE DataKinds         #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+import qualified Frames.MapReduce              as FMR
+import qualified Frames.Folds                  as FF
+
+import qualified Frames                        as F
+import qualified Data.Vinyl                    as V
+import qualified Data.List                     as L
+import qualified Data.Text                     as T
+import qualified Control.Foldl                 as FL
+import           Data.Monoid                    ( Sum )
+import           System.Random                  ( newStdGen
+                                                , randomRs
+                                                )
+-- Create types for the cols                                                
+type Label = "label" F.:-> T.Text
+type Y = "y" F.:-> Double
+type X = "x" F.:-> Double
+type AllCols = [Label,Y,X]
+
+-- filter, leaving only rows with labels 'A', 'B' or 'C'
+unpack = FMR.unpackFilterOnField @Label (`elem` ["A", "B", "C"])
+
+-- group the rest of the cols by Label
+assign = FMR.splitOnKeys @'[Label]
+
+-- sum the data columns and then re-attach the key
+reduce = FMR.foldAndAddKey $ (FF.foldAllConstrained @Num @'[Y, X]) FL.sum
+
+-- put it all together: filter, group by label, sum the data cols and re-attach the key.
+-- Then turn the resulting list of Frames (each with only one Record in this case)
+-- into one Frame via (<>).
+mrFold = FMR.concatFold $ FMR.mapReduceFold unpack assign reduce
+
+main :: IO ()
+main = do
+  f <- createFrame 1000
+  let result = FMR.fold mrFold f
+  putStrLn $ (L.intercalate "\n" $ fmap show $ FL.fold FL.list result)
+
+{- Output
+{label :-> "A", y :-> 1293.6893073755323, x :-> 1386.4314446405742}
+{label :-> "B", y :-> 1940.9402110282622, x :-> 2244.645291592506}
+{label :-> "C", y :-> 2009.8541388288395, x :-> 2128.7190606123568}
+-}
+
+--- create the Frame
+createFrame :: Int -> IO (F.FrameRec AllCols)
+createFrame n = do
+  g <- newStdGen
+  let randLabels = L.take n $ randomRs ('A', 'Z') g
+      randDbls   = L.take (2 * n) $ randomRs (0.0, 100.0) g
+      oneRow m =
+        T.singleton (randLabels !! m)
+          F.&: (randDbls !! m)
+          F.&: (randDbls !! (n + m))
+          F.&: V.RNil
+  return $ F.toFrame $ fmap oneRow [0 .. (n - 1)]
diff --git a/src/Frames/Folds.hs b/src/Frames/Folds.hs
new file mode 100644
--- /dev/null
+++ b/src/Frames/Folds.hs
@@ -0,0 +1,223 @@
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE DeriveGeneric         #-}
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE GADTs                 #-}
+{-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE TemplateHaskell       #-}
+{-# LANGUAGE TypeApplications      #-}
+{-# LANGUAGE TypeOperators         #-}
+{-# LANGUAGE RankNTypes            #-}
+{-# LANGUAGE PolyKinds             #-}
+{-# LANGUAGE TypeFamilies          #-}
+{-# LANGUAGE ConstraintKinds       #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE UndecidableInstances  #-}
+{-# LANGUAGE AllowAmbiguousTypes   #-}
+{-# LANGUAGE UndecidableSuperClasses #-}
+{-# OPTIONS_GHC -fwarn-incomplete-patterns #-}
+{-|
+Module      : Frames.Folds
+Description : Types and functions to simplify folding over Vinyl/Frames records. Leans heavily on the foldl package. 
+Copyright   : (c) Adam Conner-Sax 2019
+License     : BSD
+Maintainer  : adam_conner_sax@yahoo.com
+Stability   : experimental
+
+Frames.Folds contains various helper functions designed to simplify folding over Frames/Vinyl records given some way of folding over each column.
+-}
+module Frames.Folds
+  (
+    -- * Types
+    EndoFold
+
+    -- ** Types to act as "interpretation functors" for records of folds
+  , FoldEndo(..)
+  , FoldRecord(..)
+
+  -- * functions for building records of folds
+  , recFieldF
+
+  -- * functions for turning records of folds into folds of records
+  , sequenceRecFold
+  , sequenceEndoFolds
+
+  -- * functions using constraints to extend an endo-fold across a record
+  , foldAll
+  , foldAllConstrained
+  , foldAllMonoid
+  )
+where
+
+import qualified Control.Foldl                 as FL
+import qualified Control.Newtype               as N
+import           Data.Monoid                    ( (<>)
+                                                , Monoid(..)
+                                                )
+import qualified Data.Profunctor               as P
+import qualified Data.Vinyl                    as V
+import qualified Data.Vinyl.TypeLevel          as V
+import qualified Data.Vinyl.Functor            as V
+import qualified Frames                        as F
+import qualified Frames.Melt                   as F
+
+
+-- | A Type synonym for folds like sum or, often, average.
+type EndoFold a = FL.Fold a a
+
+-- | Turn and EndoFold a into an EndoFold (ElField '(s, a))
+fieldFold
+  :: (V.KnownField t, a ~ V.Snd t) => EndoFold a -> EndoFold (F.ElField t)
+fieldFold = P.dimap (\(V.Field x) -> x) V.Field
+{-# INLINABLE fieldFold #-}
+
+-- | Wrapper for Endo-folds of the field types of ElFields
+newtype FoldEndo t = FoldEndo { unFoldEndo :: EndoFold (V.Snd t) }
+
+-- | Wrapper for endo-folds on an interpretation f.  Usually f ~ ElField 
+newtype FoldFieldEndo f a = FoldFieldEndo { unFoldFieldEndo :: EndoFold (f a) } -- type FoldFieldEndo f a = FoldEndo (f a)
+
+-- | Wrapper for folds from a record to an interpreted field.  Usually f ~ ElField
+newtype FoldRecord f rs a = FoldRecord { unFoldRecord :: FL.Fold (F.Record rs) (f a) }
+
+-- | Helper for building a 'FoldRecord' from a given fold and function of the record
+recFieldF
+  :: forall t rs a
+   . V.KnownField t
+  => FL.Fold a (V.Snd t) -- ^ A fold from some type a to the field type of an ElField 
+  -> (F.Record rs -> a) -- ^ a function to get the a value from the input record
+  -> FoldRecord V.ElField rs t -- ^ the resulting 'FoldRecord'-wrapped fold 
+recFieldF fld fromRec = FoldRecord $ P.dimap fromRec V.Field fld
+{-# INLINABLE recFieldF #-}
+
+-- | special case of 'recFieldF' for the case when the function from the record to the folded type
+-- is just retrieving the value in a field.
+fieldToFieldFold
+  :: forall x y rs
+   . (V.KnownField x, V.KnownField y, F.ElemOf rs x)
+  => FL.Fold (V.Snd x) (V.Snd y) -- ^ the fold to be wrapped
+  -> FoldRecord F.ElField rs y -- ^ the wrapped fold
+fieldToFieldFold fld = recFieldF fld (F.rgetField @x)
+{-# INLINABLE fieldToFieldFold #-}
+
+-- | Expand a record of folds, each from the entire record to one field, into a record of folds each from a larger record to the smaller one.
+expandFoldInRecord
+  :: forall rs as
+   . (as F.⊆ rs, V.RMap as)
+  => F.Rec (FoldRecord F.ElField as) as -- ^ original fold 
+  -> F.Rec (FoldRecord F.ElField rs) as -- ^ resulting fold 
+expandFoldInRecord = V.rmap (FoldRecord . FL.premap F.rcast . unFoldRecord)
+{-# INLINABLE expandFoldInRecord #-}
+
+-- | Change a record of single field folds to a record of folds from the entire record to each field
+class EndoFieldFoldsToRecordFolds rs where
+  endoFieldFoldsToRecordFolds :: F.Rec (FoldFieldEndo F.ElField) rs -> F.Rec (FoldRecord F.ElField rs) rs
+
+
+instance EndoFieldFoldsToRecordFolds '[] where
+  endoFieldFoldsToRecordFolds _ = V.RNil
+  {-# INLINABLE endoFieldFoldsToRecordFolds #-}
+
+instance (EndoFieldFoldsToRecordFolds rs, rs F.⊆ (r ': rs), V.RMap rs) => EndoFieldFoldsToRecordFolds (r ': rs) where
+  endoFieldFoldsToRecordFolds (fe V.:& fes) = FoldRecord (FL.premap (V.rget @r) (unFoldFieldEndo fe)) V.:& expandFoldInRecord @(r ': rs) (endoFieldFoldsToRecordFolds fes)
+  {-# INLINABLE endoFieldFoldsToRecordFolds #-}
+
+-- can we do all/some of this via F.Rec (Fold as) bs?
+-- | Turn a Record of folds into a fold over records
+sequenceRecFold
+  :: forall as rs
+   . F.Rec (FoldRecord F.ElField as) rs
+  -> FL.Fold (F.Record as) (F.Record rs)
+sequenceRecFold = V.rtraverse unFoldRecord
+{-# INLINABLE sequenceRecFold #-}
+
+-- | turn a record of folds over each field, into a fold over records 
+sequenceFieldEndoFolds
+  :: EndoFieldFoldsToRecordFolds rs
+  => F.Rec (FoldFieldEndo F.ElField) rs
+  -> FL.Fold (F.Record rs) (F.Record rs)
+sequenceFieldEndoFolds = sequenceRecFold . endoFieldFoldsToRecordFolds
+{-# INLINABLE sequenceFieldEndoFolds #-}
+
+liftFold
+  :: V.KnownField t => FL.Fold (V.Snd t) (V.Snd t) -> FoldFieldEndo F.ElField t
+liftFold = FoldFieldEndo . fieldFold
+{-# INLINABLE liftFold #-}
+
+-- This is not a natural transformation, FoldEndoT ~> FoldEndo F.EField, because of the constraint
+liftFoldEndo :: V.KnownField t => FoldEndo t -> FoldFieldEndo F.ElField t
+liftFoldEndo = FoldFieldEndo . fieldFold . unFoldEndo
+{-# INLINABLE liftFoldEndo #-}
+
+liftFolds
+  :: (V.RPureConstrained V.KnownField rs, V.RApply rs)
+  => F.Rec FoldEndo rs
+  -> F.Rec (FoldFieldEndo F.ElField) rs
+liftFolds = V.rapply liftedFs
+  where liftedFs = V.rpureConstrained @V.KnownField $ V.Lift liftFoldEndo
+{-# INLINABLE liftFolds #-}
+
+
+-- | turn a record of endo-folds over each field, into a fold over records 
+sequenceEndoFolds
+  :: forall rs
+   . ( V.RApply rs
+     , V.RPureConstrained V.KnownField rs
+     , EndoFieldFoldsToRecordFolds rs
+     )
+  => F.Rec FoldEndo rs
+  -> FL.Fold (F.Record rs) (F.Record rs)
+sequenceEndoFolds = sequenceFieldEndoFolds . liftFolds
+{-# INLINABLE sequenceEndoFolds #-}
+
+-- | apply an unconstrained endo-fold, e.g., a fold which takes the last item in a container, to every field in a record
+foldAll
+  :: ( V.RPureConstrained V.KnownField rs
+     , V.RApply rs
+     , EndoFieldFoldsToRecordFolds rs
+     )
+  => (forall a . FL.Fold a a)
+  -> FL.Fold (F.Record rs) (F.Record rs)
+foldAll f = sequenceEndoFolds $ V.rpureConstrained @V.KnownField (FoldEndo f)
+{-# INLINABLE foldAll #-}
+
+class (c (V.Snd t)) => ConstrainedField c t
+instance (c (V.Snd t)) => ConstrainedField c t
+
+-- | Apply a constrained endo-fold to all fields of a record.
+-- May require a use of TypeApplications, e.g., foldAllConstrained @Num FL.sum
+foldAllConstrained
+  :: forall c rs
+   . ( V.RPureConstrained (ConstrainedField c) rs
+     , V.RPureConstrained V.KnownField rs
+     , V.RApply rs
+     , EndoFieldFoldsToRecordFolds rs
+     )
+  => (forall a . c a => FL.Fold a a)
+  -> FL.Fold (F.Record rs) (F.Record rs)
+foldAllConstrained f =
+  sequenceEndoFolds $ V.rpureConstrained @(ConstrainedField c) (FoldEndo f)
+{-# INLINABLE foldAllConstrained #-}
+
+-- | Given a monoid-wrapper, e.g., Sum, and functions to wrap and unwrap, we can produce an endo-fold on a
+monoidWrapperToFold
+  :: forall f a . (N.Newtype (f a) a, Monoid (f a)) => FL.Fold a a
+monoidWrapperToFold = FL.Fold (\w a -> N.pack a <> w) (mempty @(f a)) N.unpack -- is this the correct order in (<>) ?
+{-# INLINABLE monoidWrapperToFold #-}
+
+class (N.Newtype (f a) a, Monoid (f a)) => MonoidalField f a
+instance (N.Newtype (f a) a, Monoid (f a)) => MonoidalField f a
+
+-- | Given a monoid-wrapper, e.g., Sum, apply the derived endo-fold to all fields of a record
+-- This is strictly less powerful than foldAllConstrained but might be simpler to use in some cases
+foldAllMonoid
+  :: forall f rs
+   . ( V.RPureConstrained (ConstrainedField (MonoidalField f)) rs
+     , V.RPureConstrained V.KnownField rs
+     , V.RApply rs
+     , EndoFieldFoldsToRecordFolds rs
+     )
+  => FL.Fold (F.Record rs) (F.Record rs)
+foldAllMonoid = foldAllConstrained @(MonoidalField f) $ monoidWrapperToFold @f
+{-# INLINABLE foldAllMonoid #-}
diff --git a/src/Frames/MapReduce.hs b/src/Frames/MapReduce.hs
new file mode 100644
--- /dev/null
+++ b/src/Frames/MapReduce.hs
@@ -0,0 +1,169 @@
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE DeriveGeneric         #-}
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE GADTs                 #-}
+{-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE TemplateHaskell       #-}
+{-# LANGUAGE TypeApplications      #-}
+{-# LANGUAGE TypeOperators         #-}
+{-# LANGUAGE RankNTypes            #-}
+{-# LANGUAGE PolyKinds             #-}
+{-# LANGUAGE TypeFamilies          #-}
+{-# LANGUAGE ConstraintKinds       #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE UndecidableInstances  #-}
+{-# LANGUAGE AllowAmbiguousTypes   #-}
+{-# LANGUAGE InstanceSigs          #-}
+{-# OPTIONS_GHC -fwarn-incomplete-patterns #-}
+{-|
+Module      : Frames.MapReduce
+Description : Helpers for using the map-reduce-folds package with Frames
+Copyright   : (c) Adam Conner-Sax 2019
+License     : BSD-3-Clause
+Maintainer  : adam_conner_sax@yahoo.com
+Stability   : experimental
+
+Frames-map-reduce provides helper functions for using <https://hackage.haskell.org/package/map-reduce-folds-0.1.0.0 map-reduce-folds>
+with <http://hackage.haskell.org/package/Frames Frames>.  Please see those packages for more details.
+-}
+module Frames.MapReduce
+  (
+    -- * Unpackers
+    unpackFilterRow
+  , unpackFilterOnField
+  , unpackGoodRows
+
+    -- * Assigners
+  , assignKeysAndData
+  , assignKeys
+  , splitOnKeys
+
+  -- * Reduce and Re-Attach Key Cols
+  , reduceAndAddKey
+  , foldAndAddKey
+
+  -- * Re-Attach Key Cols
+  , makeRecsWithKey
+  , makeRecsWithKeyM
+
+  -- * Re-Exports
+  , module Control.MapReduce
+  )
+where
+
+import qualified Control.MapReduce             as MR
+import           Control.MapReduce                 -- for re-export
+
+import qualified Control.Foldl                 as FL
+import qualified Data.Foldable                 as F
+import qualified Data.Hashable                 as Hash
+import qualified Data.List                     as L
+import           Data.Monoid                    ( Monoid(..) )
+import           Data.Hashable                  ( Hashable )
+
+import qualified Frames                        as F
+import qualified Frames.Melt                   as F
+import qualified Frames.InCore                 as FI
+import qualified Data.Vinyl                    as V
+import qualified Data.Vinyl.TypeLevel          as V
+
+-- | This is only here so we can use hash maps for the grouping step.  This should properly be in Frames itself.
+instance Hash.Hashable (F.Record '[]) where
+  hash = const 0
+  {-# INLINABLE hash #-}
+  hashWithSalt s = const s -- TODO: this seems BAD! Or not?
+  {-# INLINABLE hashWithSalt #-}
+
+instance (V.KnownField t, Hash.Hashable (V.Snd t), Hash.Hashable (F.Record rs), rs F.⊆ (t ': rs)) => Hash.Hashable (F.Record (t ': rs)) where
+  hashWithSalt s r = s `Hash.hashWithSalt` (F.rgetField @t r) `Hash.hashWithSalt` (F.rcast @rs r)
+  {-# INLINABLE hashWithSalt #-}
+
+-- | Filter records using a function on the entire record. 
+unpackFilterRow
+  :: (F.Record rs -> Bool) -> MR.Unpack (F.Record rs) (F.Record rs)
+unpackFilterRow test = MR.Filter test
+
+-- | Filter records based on a condition on only one field in the row.  Will usually require a Type Application to indicate which field.
+unpackFilterOnField
+  :: forall t rs
+   . (V.KnownField t, F.ElemOf rs t)
+  => (V.Snd t -> Bool)
+  -> MR.Unpack (F.Record rs) (F.Record rs)
+unpackFilterOnField test = unpackFilterRow (test . F.rgetField @t)
+
+-- | An unpack step which specifies a subset of columns, cs, (via a type-application) and then filters a @Rec (Maybe :. Elfield) rs@
+-- to only rows which have all good data in that subset.
+unpackGoodRows
+  :: forall cs rs
+   . (cs F.⊆ rs)
+  => MR.Unpack (F.Rec (Maybe F.:. F.ElField) rs) (F.Record cs)
+unpackGoodRows = MR.Unpack $ F.recMaybe . F.rcast
+
+-- | Assign both keys and data cols.  Uses type applications to specify them if they cannot be inferred.
+-- Keys usually can't. Data sometimes can.
+assignKeysAndData
+  :: forall ks cs rs
+   . (ks F.⊆ rs, cs F.⊆ rs)
+  => MR.Assign (F.Record ks) (F.Record rs) (F.Record cs)
+assignKeysAndData = MR.assign (F.rcast @ks) (F.rcast @cs)
+{-# INLINABLE assignKeysAndData #-}
+
+-- | Assign keys and leave all columns, including the keys, in the data passed to reduce.
+assignKeys
+  :: forall ks rs
+   . (ks F.⊆ rs)
+  => MR.Assign (F.Record ks) (F.Record rs) (F.Record rs)
+assignKeys = MR.assign (F.rcast @ks) id
+{-# INLINABLE assignKeys #-}
+
+-- | Assign keys and leave the rest of the columns, excluding the keys, in the data passed to reduce.
+splitOnKeys
+  :: forall ks rs cs
+   . (ks F.⊆ rs, cs ~ F.RDeleteAll ks rs, cs F.⊆ rs)
+  => MR.Assign (F.Record ks) (F.Record rs) (F.Record cs)
+splitOnKeys = assignKeysAndData @ks @cs
+{-# INLINABLE splitOnKeys #-}
+
+-- | Reduce the data to a single row and then re-attach the key.
+reduceAndAddKey
+  :: forall ks cs x
+   . FI.RecVec ((ks V.++ cs))
+  => (forall h . Foldable h => h x -> F.Record cs) -- ^ reduction step
+  -> MR.Reduce (F.Record ks) x (F.FrameRec (ks V.++ cs))
+reduceAndAddKey process =
+  fmap (F.toFrame . pure @[]) $ MR.processAndLabel process V.rappend
+{-# INLINABLE reduceAndAddKey #-}
+
+-- | Reduce by folding the data to a single row and then re-attaching the key.
+foldAndAddKey
+  :: (FI.RecVec ((ks V.++ cs)))
+  => FL.Fold x (F.Record cs) -- ^ reduction fold
+  -> MR.Reduce (F.Record ks) x (F.FrameRec (ks V.++ cs))
+foldAndAddKey fld = fmap (F.toFrame . pure @[]) $ MR.foldAndLabel fld V.rappend  -- is Frame a reasonably fast thing for many appends?
+{-# INLINABLE foldAndAddKey #-}
+
+-- | Transform a reduce which produces a container of results, with a function from each result to a record,
+-- into a reduce which produces a FrameRec of the result records with the key re-attached.
+makeRecsWithKey
+  :: (Functor g, Foldable g, (FI.RecVec (ks V.++ as)))
+  => (y -> F.Record as) -- ^ map a result to a record
+  -> MR.Reduce (F.Record ks) x (g y) -- ^ original reduce
+  -> MR.Reduce (F.Record ks) x (F.FrameRec (ks V.++ as))
+makeRecsWithKey makeRec reduceToY = fmap F.toFrame
+  $ MR.reduceMapWithKey addKey reduceToY
+  where addKey k = fmap (V.rappend k . makeRec)
+{-# INLINABLE makeRecsWithKey #-}
+
+-- | Transform an effectful reduce which produces a container of results, with a function from each result to a record,
+-- into a reduce which produces a FrameRec of the result records with the key re-attached.
+makeRecsWithKeyM
+  :: (Monad m, Functor g, Foldable g, (FI.RecVec (ks V.++ as)))
+  => (y -> F.Record as) -- ^ map a result to a record
+  -> MR.ReduceM m (F.Record ks) x (g y) -- ^ original reduce
+  -> MR.ReduceM m (F.Record ks) x (F.FrameRec (ks V.++ as))
+makeRecsWithKeyM makeRec reduceToY = fmap F.toFrame
+  $ MR.reduceMMapWithKey addKey reduceToY
+  where addKey k = fmap (V.rappend k . makeRec)
+{-# INLINABLE makeRecsWithKeyM #-}
