packages feed

Frames-map-reduce (empty) → 0.1.0.0

raw patch · 6 files changed

+540/−0 lines, 6 filesdep +Framesdep +Frames-map-reducedep +base

Dependencies added: Frames, Frames-map-reduce, base, containers, foldl, hashable, map-reduce-folds, newtype, profunctors, random, text, vinyl

Files

+ CHANGELOG.md view
@@ -0,0 +1,2 @@+v 0.1.0.0+* Initial hackage release
+ Frames-map-reduce.cabal view
@@ -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
+ LICENSE view
@@ -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.
+ examples/AddRowsByLabel.hs view
@@ -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)]
+ src/Frames/Folds.hs view
@@ -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 #-}
+ src/Frames/MapReduce.hs view
@@ -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 #-}