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 +2/−0
- Frames-map-reduce.cabal +54/−0
- LICENSE +30/−0
- examples/AddRowsByLabel.hs +62/−0
- src/Frames/Folds.hs +223/−0
- src/Frames/MapReduce.hs +169/−0
+ 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 #-}