on master help sign in
Refactor Fold.Container and expose some container ops

Make the container operations less polymorphic.

commit6ca9d800069bf74558bc622c87b34e7ff14398e2 authorHarendra Kumar <harendra@composewell.com> dateTue 27 Dec 2022, 05:07 (4 years ago) parent46361ad
4 files changed+298−202
benchmark/Streamly/Benchmark/Data/Fold.hs+59−59 core/src/Streamly/Data/Fold.hs+19−3 core/src/Streamly/Internal/Data/Fold/Container.hs+215−135 test/Streamly/Test/Data/Fold.hs+5−5
benchmark/Streamly/Benchmark/Data/Fold.hs+59−59
@@ -31,7 +31,7 @@ import Streamly.Internal.Data.IsMap.HashMap ()
3131
3232import qualified Streamly.Internal.Data.Array.Mut.Type as MArray
3333import qualified Streamly.Internal.Data.Fold as FL
34−import qualified Streamly.Internal.Data.Fold.Extra as FL
34+import qualified Streamly.Internal.Data.Fold.Container as FL
3535import qualified Streamly.Internal.Data.Unfold as Unfold
3636import qualified Streamly.Internal.Data.Pipe as Pipe
3737import qualified Streamly.Internal.Data.Stream as Stream
@@ -203,55 +203,55 @@ partitionByMinM :: Monad m => Stream m Int -> m (Int, Int)
203203partitionByMinM =
204204 Stream.fold (FL.partitionByMinM (return . oddEven) FL.sum FL.length)
205205
206−{-# INLINE demuxWith #-}
207−demuxWith :: (Monad m, Ord k) =>
206+{-# INLINE demuxToMap #-}
207+demuxToMap :: (Monad m, Ord k) =>
208208 (a -> k) -> (a -> m (Fold m a b)) -> Stream m a -> m (Map k b)
209−demuxWith f g = Stream.fold (FL.demuxWith f g)
209+demuxToMap f g = Stream.fold (FL.demuxToContainer f g)
210210
211−{-# INLINE demuxWithInt #-}
212−demuxWithInt :: Monad m =>
211+{-# INLINE demuxToIntMap #-}
212+demuxToIntMap :: Monad m =>
213213 (a -> Int) -> (a -> m (Fold m a b)) -> Stream m a -> m (IntMap b)
214−demuxWithInt f g = Stream.fold (FL.demuxWith f g)
214+demuxToIntMap f g = Stream.fold (FL.demuxToContainer f g)
215215
216−{-# INLINE demuxWithHash #-}
217−demuxWithHash :: (Monad m, Ord k, Hashable k) =>
216+{-# INLINE demuxToHashMap #-}
217+demuxToHashMap :: (Monad m, Ord k, Hashable k) =>
218218 (a -> k) -> (a -> m (Fold m a b)) -> Stream m a -> m (HashMap k b)
219−demuxWithHash f g = Stream.fold (FL.demuxWith f g)
219+demuxToHashMap f g = Stream.fold (FL.demuxToContainer f g)
220220
221−{-# INLINE demuxMutWith #-}
222−demuxMutWith :: (MonadIO m, Ord k) =>
221+{-# INLINE demuxToMapIO #-}
222+demuxToMapIO :: (MonadIO m, Ord k) =>
223223 (a -> k) -> (a -> m (Fold m a b)) -> Stream m a -> m (Map k b)
224−demuxMutWith f g = Stream.fold (FL.demuxMutWith f g)
224+demuxToMapIO f g = Stream.fold (FL.demuxToContainerIO f g)
225225
226−{-# INLINE demuxMutWithHash #-}
227−demuxMutWithHash :: (MonadIO m, Ord k, Hashable k) =>
226+{-# INLINE demuxToHashMapIO #-}
227+demuxToHashMapIO :: (MonadIO m, Ord k, Hashable k) =>
228228 (a -> k) -> (a -> m (Fold m a b)) -> Stream m a -> m (HashMap k b)
229−demuxMutWithHash f g = Stream.fold (FL.demuxMutWith f g)
229+demuxToHashMapIO f g = Stream.fold (FL.demuxToContainerIO f g)
230230
231−{-# INLINE classifyWith #-}
232−classifyWith ::
231+{-# INLINE toMap #-}
232+toMap ::
233233 (Monad m, Ord k, Num a) => (a -> k) -> Stream m a -> m (Map k a)
234−classifyWith f = Stream.fold (FL.classifyWith f FL.sum)
234+toMap f = Stream.fold (FL.toContainer f FL.sum)
235235
236−{-# INLINE classifyWithInt #-}
237−classifyWithInt ::
236+{-# INLINE toIntMap #-}
237+toIntMap ::
238238 (Monad m, Num a) => (a -> Int) -> Stream m a -> m (IntMap a)
239−classifyWithInt f = Stream.fold (FL.classifyWith f FL.sum)
239+toIntMap f = Stream.fold (FL.toContainer f FL.sum)
240240
241−{-# INLINE classifyMutWith #-}
242−classifyMutWith ::
241+{-# INLINE toMapIO #-}
242+toMapIO ::
243243 (MonadIO m, Ord k, Num a) => (a -> k) -> Stream m a -> m (Map k a)
244−classifyMutWith f = Stream.fold (FL.classifyMutWith f FL.sum)
244+toMapIO f = Stream.fold (FL.toContainerIO f FL.sum)
245245
246−{-# INLINE classifyMutWithInt #-}
247−classifyMutWithInt ::
246+{-# INLINE toIntMapIO #-}
247+toIntMapIO ::
248248 (MonadIO m, Num a) => (a -> Int) -> Stream m a -> m (IntMap a)
249−classifyMutWithInt f = Stream.fold (FL.classifyMutWith f FL.sum)
249+toIntMapIO f = Stream.fold (FL.toContainerIO f FL.sum)
250250
251−{-# INLINE classifyMutWithHash #-}
252−classifyMutWithHash :: (MonadIO m, Ord k, Num a, Hashable k) =>
251+{-# INLINE toHashMapIO #-}
252+toHashMapIO :: (MonadIO m, Ord k, Num a, Hashable k) =>
253253 (a -> k) -> Stream m a -> m (HashMap k a)
254−classifyMutWithHash f = Stream.fold (FL.classifyMutWith f FL.sum)
254+toHashMapIO f = Stream.fold (FL.toContainerIO f FL.sum)
255255
256256-------------------------------------------------------------------------------
257257-- unzip
@@ -452,38 +452,38 @@ o_n_heap_serial value =
452452 ]
453453 , bgroup "key-value"
454454 [
455− benchIOSink value "demuxWith (64 buckets) [sum, length]"
456− $ demuxWith (getKey 64) (getFold . getKey 64)
457− , benchIOSink value "demuxWithInt (64 buckets) [sum, length]"
458− $ demuxWithInt (getKey 64) (getFold . getKey 64)
459− , benchIOSink value "demuxWithHash (64 buckets) [sum, length]"
460− $ demuxWithHash (getKey 64) (getFold . getKey 64)
461− , benchIOSink value "demuxMutWith (64 buckets) [sum, length]"
462− $ demuxMutWith (getKey 64) (getFold . getKey 64)
463− , benchIOSink value "demuxMutWithHash (64 buckets) [sum, length]"
464− $ demuxMutWithHash (getKey 64) (getFold . getKey 64)
455+ benchIOSink value "demuxToMap (64 buckets) [sum, length]"
456+ $ demuxToMap (getKey 64) (getFold . getKey 64)
457+ , benchIOSink value "demuxToIntMap (64 buckets) [sum, length]"
458+ $ demuxToIntMap (getKey 64) (getFold . getKey 64)
459+ , benchIOSink value "demuxToHashMap (64 buckets) [sum, length]"
460+ $ demuxToHashMap (getKey 64) (getFold . getKey 64)
461+ , benchIOSink value "demuxToMapIO (64 buckets) [sum, length]"
462+ $ demuxToMapIO (getKey 64) (getFold . getKey 64)
463+ , benchIOSink value "demuxToHashMapIO (64 buckets) [sum, length]"
464+ $ demuxToHashMapIO (getKey 64) (getFold . getKey 64)
465465
466466 -- classify: immutable
467− , benchIOSink value "classifyWith (64 buckets) sum"
468− $ classifyWith (getKey 64)
469− , benchIOSink value "classifyWithInt (64 buckets) sum"
470− $ classifyWithInt (getKey 64)
467+ , benchIOSink value "toMap (64 buckets) sum"
468+ $ toMap (getKey 64)
469+ , benchIOSink value "toIntMap (64 buckets) sum"
470+ $ toIntMap (getKey 64)
471471
472472 -- classify: mutable cells
473− , benchIOSink value "classifyMutWith (single bucket) sum"
474− $ classifyMutWith (getKey 1)
475− , benchIOSink value "classifyMutWith (64 buckets) sum"
476− $ classifyMutWith (getKey 64)
477− , benchIOSink value "classifyMutWith (max buckets) sum"
478− $ classifyMutWith (getKey value)
479− , benchIOSink value "classifyMutWithInt (64 buckets) sum"
480− $ classifyMutWithInt (getKey 64)
481− , benchIOSink value "classifyMutWithHash (single bucket) sum"
482− $ classifyMutWithHash (getKey 1)
483− , benchIOSink value "classifyMutWithHash (64 buckets) sum"
484− $ classifyMutWithHash (getKey 64)
485− , benchIOSink value "classifyMutWithHash (max buckets) sum"
486− $ classifyMutWithHash (getKey value)
473+ , benchIOSink value "toMapIO (single bucket) sum"
474+ $ toMapIO (getKey 1)
475+ , benchIOSink value "toMapIO (64 buckets) sum"
476+ $ toMapIO (getKey 64)
477+ , benchIOSink value "toMapIO (max buckets) sum"
478+ $ toMapIO (getKey value)
479+ , benchIOSink value "toIntMapIO (64 buckets) sum"
480+ $ toIntMapIO (getKey 64)
481+ , benchIOSink value "toHashMapIO (single bucket) sum"
482+ $ toHashMapIO (getKey 1)
483+ , benchIOSink value "toHashMapIO (64 buckets) sum"
484+ $ toHashMapIO (getKey 64)
485+ , benchIOSink value "toHashMapIO (max buckets) sum"
486+ $ toHashMapIO (getKey value)
487487 ]
488488 ]
489489
core/src/Streamly/Data/Fold.hs+19−3
@@ -156,6 +156,9 @@ module Streamly.Data.Fold
156156 , drain
157157 , drainMapM
158158 , length
159+ , countDistinct
160+ , countDistinctInt
161+ , frequency
159162 , sum
160163 , product
161164 , mean
@@ -165,6 +168,12 @@ module Streamly.Data.Fold
165168 -- Collectors
166169 , toList
167170 , toListRev
171+ , toSet
172+ , toIntSet
173+ , toMap
174+ , toMapIO
175+ , demuxToMap
176+ , demuxToMapIO
168177
169178 -- ** Non-Empty Accumulators
170179 -- | Accumulators that do not have a default value, therefore, return
@@ -178,11 +187,17 @@ module Streamly.Data.Fold
178187 -- ** Filtering Scanners
179188 -- | Accumulators that are usually run as a scan using the 'scanMaybe'
180189 -- combinator.
181− , uniq
182− , uniqBy
183− , deleteBy
184190 , findIndices
185191 , elemIndices
192+ , deleteBy
193+ -- , uniq
194+ , uniqBy
195+ , nub
196+ , nubInt
197+ , classify
198+ , classifyIO
199+ , demux
200+ , demuxIO
186201
187202 -- ** Terminating Folds
188203 -- | These are much like lazy right folds.
@@ -340,6 +355,7 @@ import Prelude
340355 span, splitAt, break, mapM, maybe)
341356
342357import Streamly.Internal.Data.Fold
358+import Streamly.Internal.Data.Fold.Container
343359
344360-- $setup
345361-- >>> import Data.Function ((&))
core/src/Streamly/Internal/Data/Fold/Container.hs+215−135
@@ -12,27 +12,38 @@ module Streamly.Internal.Data.Fold.Container
1212 -- * Imports
1313 -- $setup
1414
15− countDistinct
15+ -- * Set operations
16+ toSet
17+ , toIntSet
18+ , countDistinct
1619 , countDistinctInt
17− , frequency
18− , toMap
1920 , nub
2021 , nubInt
2122
23+ -- * Map operations
24+ , frequency
25+
2226 -- ** Demultiplexing
2327 -- | Direct values in the input stream to different folds using an n-ary
2428 -- fold selector. 'demux' is a generalization of 'classify' (and
2529 -- 'partition') where each key of the classifier can use a different fold.
30+ , demuxKvToContainer
31+ , demuxKvToMap
32+
33+ , demuxToContainer
34+ , demuxToContainerIO
35+ , demuxToMap
36+ , demuxToMapIO
37+
38+ , demuxGeneric
2639 , demux
27− , demuxWith
28− , demuxScanWith
29− , demuxScanMutWith
30− , demuxMutWith
40+ , demuxGenericIO
41+ , demuxIO
3142
3243 -- TODO: These can be implemented using the above operations
33− -- , demuxWithSel -- Stop when the fold for the specified key stops
34− -- , demuxWithMin -- Stop when any of the folds stop
35− -- , demuxWithAll -- Stop when all the folds stop (run once)
44+ -- , demuxSel -- Stop when the fold for the specified key stops
45+ -- , demuxMin -- Stop when any of the folds stop
46+ -- , demuxAll -- Stop when all the folds stop (run once)
3647
3748 -- ** Classifying
3849 -- | In an input stream of key value pairs fold values for different keys
@@ -47,12 +58,19 @@ module Streamly.Internal.Data.Fold.Container
4758 -- and their meanings then trie Map would be better if we also want to
4859 -- display them in sorted order.
4960
61+ , kvToMap
62+
63+ , toContainer
64+ , toContainerIO
65+ , toMap
66+ , toMapIO
67+
68+ , classifyGeneric
5069 , classify
51− , classifyWith
52− , classifyMutWith
53− , classifyScanWith
54− -- , classifyWithSel
55− -- , classifyWithMin
70+ , classifyGenericIO
71+ , classifyIO
72+ -- , toContainerSel
73+ -- , toContainerMin
5674 )
5775where
5876
@@ -62,29 +80,48 @@ where
6280import Control.Monad.IO.Class (MonadIO(..))
6381import Data.IORef (newIORef, readIORef, writeIORef)
6482import Data.Map.Strict (Map)
83+import Data.IntSet (IntSet)
84+import Data.Set (Set)
6585import Streamly.Internal.Data.IsMap (IsMap(..))
6686import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))
6787
6888import qualified Data.IntSet as IntSet
6989import qualified Data.Set as Set
70−import qualified Prelude
7190import qualified Streamly.Internal.Data.IsMap as IsMap
7291
73−import Prelude hiding
74− ( filter, foldl1, drop, dropWhile, take, takeWhile, zipWith
75− , foldl, foldr, map, mapM_, sequence, all, any, sum, product, elem
76− , notElem, maximum, minimum, head, last, tail, length, null
77− , reverse, iterate, init, and, or, lookup, (!!)
78− , scanl, scanl1, replicate, concatMap, mconcat, foldMap, unzip
79− , span, splitAt, break, mapM, zip, maybe)
92+import Prelude hiding (length)
8093import Streamly.Internal.Data.Fold
8194
8295-- $setup
8396-- >>> :m
8497-- >>> :set -XFlexibleContexts
98+-- >>> import qualified Data.Map as Map
99+-- >>> import qualified Data.Set as Set
100+-- >>> import qualified Data.IntSet as IntSet
85101-- >>> import qualified Streamly.Data.Fold as Fold
86102-- >>> import qualified Streamly.Data.Stream as Stream
87−-- >>> import qualified Streamly.Internal.Data.Fold.Extra as Fold
103+-- >>> import qualified Streamly.Internal.Data.Fold.Container as Fold
104+
105+-- | Fold the input to a set.
106+--
107+-- Definition:
108+--
109+-- >>> toSet = Fold.foldl' (flip Set.insert) Set.empty
110+--
111+{-# INLINE toSet #-}
112+toSet :: (Monad m, Ord a) => Fold m a (Set a)
113+toSet = foldl' (flip Set.insert) Set.empty
114+
115+-- | Fold the input to an int set. For integer inputs this performs better than
116+-- 'toSet'.
117+--
118+-- Definition:
119+--
120+-- >>> toIntSet = Fold.foldl' (flip IntSet.insert) IntSet.empty
121+--
122+{-# INLINE toIntSet #-}
123+toIntSet :: Monad m => Fold m Int IntSet
124+toIntSet = foldl' (flip IntSet.insert) IntSet.empty
88125
89126-- XXX Name as nubOrd? Or write a nubGeneric
90127
@@ -113,10 +150,6 @@ nub = fmap (\(Tuple' _ x) -> x) $ foldl' step initial
113150
114151-- | Like 'nub' but specialized to a stream of 'Int', for better performance.
115152--
116−-- Definition:
117−--
118−-- >>> nubInt = Fold.nub
119−--
120153-- /Pre-release/
121154{-# INLINE nubInt #-}
122155nubInt :: Monad m => Fold m Int (Maybe Int)
@@ -139,6 +172,7 @@ nubInt = fmap (\(Tuple' _ x) -> x) $ foldl' step initial
139172--
140173-- Definition:
141174--
175+-- >>> countDistinct = fmap Set.size Fold.toSet
142176-- >>> countDistinct = Fold.postscan Fold.nub $ Fold.catMaybes $ Fold.length
143177--
144178-- The memory used is proportional to the number of distinct elements in the
@@ -151,7 +185,8 @@ nubInt = fmap (\(Tuple' _ x) -> x) $ foldl' step initial
151185--
152186{-# INLINE countDistinct #-}
153187countDistinct :: (Monad m, Ord a) => Fold m a Int
154−countDistinct = postscan nub $ catMaybes length
188+-- countDistinct = postscan nub $ catMaybes length
189+countDistinct = fmap Set.size toSet
155190{-
156191countDistinct = fmap (\(Tuple' _ n) -> n) $ foldl' step initial
157192
@@ -173,12 +208,14 @@ countDistinct = fmap (\(Tuple' _ n) -> n) $ foldl' step initial
173208--
174209-- Definition:
175210--
211+-- >>> countDistinctInt = fmap IntSet.size Fold.toIntSet
176212-- >>> countDistinctInt = Fold.postscan Fold.nubInt $ Fold.catMaybes $ Fold.length
177213--
178214-- /Pre-release/
179215{-# INLINE countDistinctInt #-}
180216countDistinctInt :: Monad m => Fold m Int Int
181−countDistinctInt = postscan nubInt $ catMaybes length
217+-- countDistinctInt = postscan nubInt $ catMaybes length
218+countDistinctInt = fmap IntSet.size toIntSet
182219{-
183220countDistinctInt = fmap (\(Tuple' _ n) -> n) $ foldl' step initial
184221
@@ -214,23 +251,12 @@ countDistinctInt = fmap (\(Tuple' _ n) -> n) $ foldl' step initial
214251-- because key is to be determined on each input whereas fold is to be
215252-- determined only once for a key.
216253
217−-- | In a key value stream, fold values corresponding to each key with a key
218−-- specific fold. The fold returns the fold result as the second component of
219−-- the output tuple whenever a fold terminates. The first component of the
220−-- tuple is a container of in-progress folds. If a fold terminates, another
221−-- instance of the fold is started upon receiving an input with that key.
222−--
223−-- This can be used to scan a stream and collect the results from the scan
224−-- output.
225−--
226−-- /Pre-release/
227−--
228−{-# INLINE demuxScanWith #-}
229−demuxScanWith :: (Monad m, IsMap f, Traversable f) =>
254+{-# INLINE demuxGeneric #-}
255+demuxGeneric :: (Monad m, IsMap f, Traversable f) =>
230256 (a -> Key f)
231257 -> (a -> m (Fold m a b))
232258 -> Fold m a (m (f b), Maybe (Key f, b))
233−demuxScanWith getKey getFold = fmap extract $ foldlM' step initial
259+demuxGeneric getKey getFold = fmap extract $ foldlM' step initial
234260
235261 where
236262
@@ -268,19 +294,30 @@ demuxScanWith getKey getFold = fmap extract $ foldlM' step initial
268294 Partial s -> e s
269295 Done b -> return b
270296
271−-- | This is specialized version of 'demuxScanWith' that uses mutable cells as
272−-- fold accumulators for better performance.
297+-- | In a key value stream, fold values corresponding to each key with a key
298+-- specific fold. The fold returns the fold result as the second component of
299+-- the output tuple whenever a fold terminates. The first component of the
300+-- tuple is a Map of in-progress folds. If a fold terminates, another
301+-- instance of the fold is started upon receiving an input with that key.
273302--
274−-- Definition:
303+-- This can be used to scan a stream and collect the results from the scan
304+-- output.
275305--
276−-- >>> demuxScanMutWith = Fold.demuxScanWith
306+-- /Pre-release/
277307--
278−{-# INLINE demuxScanMutWith #-}
279−demuxScanMutWith :: (MonadIO m, IsMap f, Traversable f) =>
308+{-# INLINE demux #-}
309+demux :: (Monad m, Ord k) =>
310+ (a -> k)
311+ -> (a -> m (Fold m a b))
312+ -> Fold m a (m (Map k b), Maybe (k, b))
313+demux = demuxGeneric
314+
315+{-# INLINE demuxGenericIO #-}
316+demuxGenericIO :: (MonadIO m, IsMap f, Traversable f) =>
280317 (a -> Key f)
281318 -> (a -> m (Fold m a b))
282319 -> Fold m a (m (f b), Maybe (Key f, b))
283−demuxScanMutWith getKey getFold = fmap extract $ foldlM' step initial
320+demuxGenericIO getKey getFold = fmap extract $ foldlM' step initial
284321
285322 where
286323
@@ -314,7 +351,7 @@ demuxScanMutWith getKey getFold = fmap extract $ foldlM' step initial
314351 Done b ->
315352 let kv1 = IsMap.mapDelete k kv
316353 in return $ Tuple' kv1 (Just (k, b))
317− Done _ -> error "demuxScanMutWith: unreachable"
354+ Done _ -> error "demuxGenericIO: unreachable"
318355
319356 step (Tuple' kv _) a = do
320357 let k = getKey a
@@ -337,47 +374,68 @@ demuxScanMutWith getKey getFold = fmap extract $ foldlM' step initial
337374 Partial s -> e s
338375 Done b -> return b
339376
340−-- | This collects all the results of 'demuxScanWith' in a container.
377+-- | This is specialized version of 'demux' that uses mutable IO cells as
378+-- fold accumulators for better performance.
341379--
342−{-# INLINE demuxWith #-}
343−demuxWith :: (Monad m, IsMap f, Traversable f) =>
380+{-# INLINE demuxIO #-}
381+demuxIO :: (MonadIO m, Ord k) =>
382+ (a -> k)
383+ -> (a -> m (Fold m a b))
384+ -> Fold m a (m (Map k b), Maybe (k, b))
385+demuxIO = demuxGenericIO
386+
387+{-# INLINE demuxToContainer #-}
388+demuxToContainer :: (Monad m, IsMap f, Traversable f) =>
344389 (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (f b)
345−demuxWith getKey getFold =
390+demuxToContainer getKey getFold =
346391 let
347− classifier = demuxScanWith getKey getFold
392+ classifier = demuxGeneric getKey getFold
348393 getMap Nothing = pure IsMap.mapEmpty
349394 getMap (Just action) = action
350395 aggregator =
351396 teeWith IsMap.mapUnion
352397 (rmapM getMap $ lmap fst latest)
353− (lmap snd $ catMaybes toMap)
398+ (lmap snd $ catMaybes kvToMapOverwriteGeneric)
354399 in postscan classifier aggregator
355400
356−-- | Same as 'demuxWith' but uses 'demuxScanMutWith' for better performance.
401+-- | This collects all the results of 'demux' in a Map.
357402--
358−-- Definition:
359−--
360−-- >>> demuxMutWith = Fold.demuxWith
361−--
362−{-# INLINE demuxMutWith #-}
363−demuxMutWith :: (MonadIO m, IsMap f, Traversable f) =>
403+{-# INLINE demuxToMap #-}
404+demuxToMap :: (Monad m, Ord k) =>
405+ (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (Map k b)
406+demuxToMap = demuxToContainer
407+
408+{-# INLINE demuxToContainerIO #-}
409+demuxToContainerIO :: (MonadIO m, IsMap f, Traversable f) =>
364410 (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (f b)
365−demuxMutWith getKey getFold =
411+demuxToContainerIO getKey getFold =
366412 let
367− classifier = demuxScanMutWith getKey getFold
413+ classifier = demuxGenericIO getKey getFold
368414 getMap Nothing = pure IsMap.mapEmpty
369415 getMap (Just action) = action
370416 aggregator =
371417 teeWith IsMap.mapUnion
372418 (rmapM getMap $ lmap fst latest)
373− (lmap snd $ catMaybes toMap)
419+ (lmap snd $ catMaybes kvToMapOverwriteGeneric)
374420 in postscan classifier aggregator
375421
422+-- | Same as 'demuxToMap' but uses 'demuxIO' for better performance.
423+--
424+{-# INLINE demuxToMapIO #-}
425+demuxToMapIO :: (MonadIO m, Ord k) =>
426+ (a -> k) -> (a -> m (Fold m a b)) -> Fold m a (Map k b)
427+demuxToMapIO = demuxToContainerIO
428+
429+{-# INLINE demuxKvToContainer #-}
430+demuxKvToContainer :: (Monad m, IsMap f, Traversable f) =>
431+ (Key f -> m (Fold m a b)) -> Fold m (Key f, a) (f b)
432+demuxKvToContainer f = demuxToContainer fst (\(k, _) -> fmap (lmap snd) (f k))
433+
376434-- | Fold a stream of key value pairs using a function that maps keys to folds.
377435--
378436-- Definition:
379437--
380−-- >>> demux f = Fold.demuxWith fst (Fold.lmap snd . f)
438+-- >>> demuxKvToMap f = Fold.demuxToContainer fst (Fold.lmap snd . f)
381439--
382440-- Example:
383441--
@@ -386,15 +444,15 @@ demuxMutWith getKey getFold =
386444-- let f "SUM" = return Fold.sum
387445-- f _ = return Fold.product
388446-- input = Stream.fromList [("SUM",1),("PRODUCT",2),("SUM",3),("PRODUCT",4)]
389−-- in Stream.fold (Fold.demux f) input :: IO (Map String Int)
447+-- in Stream.fold (Fold.demuxKvToMap f) input :: IO (Map String Int)
390448-- :}
391449-- fromList [("PRODUCT",8),("SUM",4)]
392450--
393451-- /Pre-release/
394−{-# INLINE demux #-}
395−demux :: (Monad m, IsMap f, Traversable f) =>
396− (Key f -> m (Fold m a b)) -> Fold m (Key f, a) (f b)
397−demux f = demuxWith fst (\(k, _) -> fmap (lmap snd) (f k))
452+{-# INLINE demuxKvToMap #-}
453+demuxKvToMap :: (Monad m, Ord k) =>
454+ (k -> m (Fold m a b)) -> Fold m (k, a) (Map k b)
455+demuxKvToMap = demuxKvToContainer
398456
399457------------------------------------------------------------------------------
400458-- Classify: Like demux but uses the same fold for all keys.
@@ -406,25 +464,14 @@ demux f = demuxWith fst (\(k, _) -> fmap (lmap snd) (f k))
406464-- done then initial would set a flag in the state to ignore the input or
407465-- return an error.
408466
409−-- | Folds the values for each key using the supplied fold. When scanning, as
410−-- soon as the fold is complete, its result is available in the second
411−-- component of the tuple. The first component of the tuple is a snapshot of
412−-- the in-progress folds.
413−--
414−-- Once the fold for a key is done, any future values of the key are ignored.
415−--
416−-- Definition:
417−--
418−-- >>> classifyScanWith f fld = Fold.demuxScanWith f (const fld)
419−--
420−{-# INLINE classifyScanWith #-}
421−classifyScanWith :: (Monad m, IsMap f, Traversable f, Ord (Key f)) =>
467+{-# INLINE classifyGeneric #-}
468+classifyGeneric :: (Monad m, IsMap f, Traversable f, Ord (Key f)) =>
422469 -- Note: we need to return the Map itself to display the in-progress values
423470 -- e.g. to implement top. We could possibly create a separate abstraction
424471 -- for that use case. We return an action because we want it to be lazy so
425472 -- that the downstream consumers can choose to process or discard it.
426473 (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))
427−classifyScanWith f (Fold step1 initial1 extract1) =
474+classifyGeneric f (Fold step1 initial1 extract1) =
428475 fmap extract $ foldlM' step initial
429476
430477 where
@@ -464,23 +511,30 @@ classifyScanWith f (Fold step1 initial1 extract1) =
464511
465512 extract (Tuple3' kv _ x) = (Prelude.mapM extract1 kv, x)
466513
467−-- XXX we can use a Prim IORef if we can constrain the state "s" to be Prim
514+-- | Folds the values for each key using the supplied fold. When scanning, as
515+-- soon as the fold is complete, its result is available in the second
516+-- component of the tuple. The first component of the tuple is a snapshot of
517+-- the in-progress folds.
468518--
469−-- The code is almost the same as classifyScanWith except the IORef operations.
470−
471−-- | Same as classifyScanWith except that it uses mutable IORef cells in the
472−-- Map providing better performance. Be aware that if this is used as a scan,
473−-- the values in the intermediate Maps would be mutable.
519+-- Once the fold for a key is done, any future values of the key are ignored.
474520--
475−-- Definitions:
521+-- Definition:
522+--
523+-- >>> classify f fld = Fold.demux f (const fld)
476524--
477−-- >>> classifyScanMutWith = Fold.classifyScanWith
478−-- >>> classifyScanMutWith f fld = Fold.demuxScanMutWith f (const fld)
525+{-# INLINE classify #-}
526+classify :: (Monad m, Ord k) =>
527+ (a -> k) -> Fold m a b -> Fold m a (m (Map k b), Maybe (k, b))
528+classify = classifyGeneric
529+
530+-- XXX we can use a Prim IORef if we can constrain the state "s" to be Prim
479531--
480−{-# INLINE classifyScanMutWith #-}
481−classifyScanMutWith :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>
532+-- The code is almost the same as classifyGeneric except the IORef operations.
533+
534+{-# INLINE classifyGenericIO #-}
535+classifyGenericIO :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>
482536 (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))
483−classifyScanMutWith f (Fold step1 initial1 extract1) =
537+classifyGenericIO f (Fold step1 initial1 extract1) =
484538 fmap extract $ foldlM' step initial
485539
486540 where
@@ -523,11 +577,39 @@ classifyScanMutWith f (Fold step1 initial1 extract1) =
523577 extract (Tuple3' kv _ x) =
524578 (Prelude.mapM (\ref -> liftIO (readIORef ref) >>= extract1) kv, x)
525579
526−-- | Fold a key value stream to a key-value container. If the same key appears
580+-- | Same as classify except that it uses mutable IORef cells in the
581+-- Map providing better performance. Be aware that if this is used as a scan,
582+-- the values in the intermediate Maps would be mutable.
583+--
584+-- Definitions:
585+--
586+-- >>> classifyIO f fld = Fold.demuxIO f (const fld)
587+--
588+{-# INLINE classifyIO #-}
589+classifyIO :: (MonadIO m, Ord k) =>
590+ (a -> k) -> Fold m a b -> Fold m a (m (Map k b), Maybe (k, b))
591+classifyIO = classifyGenericIO
592+
593+-- | Fold a key value stream to a key-value Map. If the same key appears
527594-- multiple times, only the last value is retained.
528−{-# INLINE toMap #-}
529−toMap :: (Monad m, IsMap f) => Fold m (Key f, a) (f a)
530−toMap = foldl' (\kv (k, v) -> IsMap.mapInsert k v kv) IsMap.mapEmpty
595+{-# INLINE kvToMapOverwriteGeneric #-}
596+kvToMapOverwriteGeneric :: (Monad m, IsMap f) => Fold m (Key f, a) (f a)
597+kvToMapOverwriteGeneric =
598+ foldl' (\kv (k, v) -> IsMap.mapInsert k v kv) IsMap.mapEmpty
599+
600+{-# INLINE toContainer #-}
601+toContainer :: (Monad m, IsMap f, Traversable f, Ord (Key f)) =>
602+ (a -> Key f) -> Fold m a b -> Fold m a (f b)
603+toContainer f fld =
604+ let
605+ classifier = classifyGeneric f fld
606+ getMap Nothing = pure IsMap.mapEmpty
607+ getMap (Just action) = action
608+ aggregator =
609+ teeWith IsMap.mapUnion
610+ (rmapM getMap $ lmap fst latest)
611+ (lmap snd $ catMaybes kvToMapOverwriteGeneric)
612+ in postscan classifier aggregator
531613
532614-- | Split the input stream based on a key field and fold each split using the
533615-- given fold. Useful for map/reduce, bucketizing the input in different bins
@@ -538,7 +620,7 @@ toMap = foldl' (\kv (k, v) -> IsMap.mapInsert k v kv) IsMap.mapEmpty
538620-- >>> import Data.Map.Strict (Map)
539621-- >>> :{
540622-- let input = Stream.fromList [("ONE",1),("ONE",1.1),("TWO",2), ("TWO",2.2)]
541−-- classify = Fold.classifyWith fst (Fold.lmap snd Fold.toList)
623+-- classify = Fold.toMap fst (Fold.lmap snd Fold.toList)
542624-- in Stream.fold classify input :: IO (Map String [Double])
543625-- :}
544626-- fromList [("ONE",[1.0,1.1]),("TWO",[2.0,2.2])]
@@ -550,44 +632,42 @@ toMap = foldl' (\kv (k, v) -> IsMap.mapInsert k v kv) IsMap.mapEmpty
550632-- monotonically increases because it stores whether a key has been seen or
551633-- not.
552634--
635+-- See 'demuxToMap' for a more powerful version where you can use a different
636+-- fold for each key. A simpler version of 'toMap' retaining only the last
637+-- value for a key can be written as:
638+--
639+-- >>> toMap = Fold.foldl' (\kv (k, v) -> Map.insert k v kv) Map.empty
640+--
553641-- /Stops: never/
554642--
555643-- /Pre-release/
556644--
557−{-# INLINE classifyWith #-}
558−classifyWith :: (Monad m, IsMap f, Traversable f, Ord (Key f)) =>
645+{-# INLINE toMap #-}
646+toMap :: (Monad m, Ord k) =>
647+ (a -> k) -> Fold m a b -> Fold m a (Map k b)
648+toMap = toContainer
649+
650+{-# INLINE toContainerIO #-}
651+toContainerIO :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>
559652 (a -> Key f) -> Fold m a b -> Fold m a (f b)
560−classifyWith f fld =
653+toContainerIO f fld =
561654 let
562− classifier = classifyScanWith f fld
655+ classifier = classifyGenericIO f fld
563656 getMap Nothing = pure IsMap.mapEmpty
564657 getMap (Just action) = action
565658 aggregator =
566659 teeWith IsMap.mapUnion
567660 (rmapM getMap $ lmap fst latest)
568− (lmap snd $ catMaybes toMap)
661+ (lmap snd $ catMaybes kvToMapOverwriteGeneric)
569662 in postscan classifier aggregator
570663
571−-- | Same as 'classifyWith' but maybe faster because it uses mutable cells as
664+-- | Same as 'toMap' but maybe faster because it uses mutable cells as
572665-- fold accumulators in the Map.
573666--
574−-- Definition:
575−--
576−-- >>> classifyMutWith = Fold.classifyWith
577−--
578−{-# INLINE classifyMutWith #-}
579−classifyMutWith :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>
580− (a -> Key f) -> Fold m a b -> Fold m a (f b)
581−classifyMutWith f fld =
582− let
583− classifier = classifyScanMutWith f fld
584− getMap Nothing = pure IsMap.mapEmpty
585− getMap (Just action) = action
586− aggregator =
587− teeWith IsMap.mapUnion
588− (rmapM getMap $ lmap fst latest)
589− (lmap snd $ catMaybes toMap)
590− in postscan classifier aggregator
667+{-# INLINE toMapIO #-}
668+toMapIO :: (MonadIO m, Ord k) =>
669+ (a -> k) -> Fold m a b -> Fold m a (Map k b)
670+toMapIO = toContainerIO
591671
592672-- | Given an input stream of key value pairs and a fold for values, fold all
593673-- the values belonging to each key. Useful for map/reduce, bucketizing the
@@ -595,20 +675,20 @@ classifyMutWith f fld =
595675--
596676-- Definition:
597677--
598−-- >>> classify = Fold.classifyWith fst . Fold.lmap snd
678+-- >>> kvToMap = Fold.toMap fst . Fold.lmap snd
599679--
600680-- Example:
601681--
602682-- >>> :{
603683-- let input = Stream.fromList [("ONE",1),("ONE",1.1),("TWO",2), ("TWO",2.2)]
604−-- in Stream.fold (Fold.classify Fold.toList) input
684+-- in Stream.fold (Fold.kvToMap Fold.toList) input
605685-- :}
606686-- fromList [("ONE",[1.0,1.1]),("TWO",[2.0,2.2])]
607687--
608688-- /Pre-release/
609−{-# INLINE classify #-}
610−classify :: (Monad m, Ord k) => Fold m a b -> Fold m (k, a) (Map k b)
611−classify = classifyWith fst . lmap snd
689+{-# INLINE kvToMap #-}
690+kvToMap :: (Monad m, Ord k) => Fold m a b -> Fold m (k, a) (Map k b)
691+kvToMap = toMap fst . lmap snd
612692
613693-- | Determine the frequency of each element in the stream.
614694--
@@ -617,8 +697,8 @@ classify = classifyWith fst . lmap snd
617697--
618698-- Definition:
619699--
620−-- >>> frequency = Fold.classifyWith id Fold.length
700+-- >>> frequency = Fold.toMap id Fold.length
621701--
622702{-# INLINE frequency #-}
623703frequency :: (Monad m, Ord a) => Fold m a (Map a Int)
624−frequency = classifyWith id length
704+frequency = toMap id length
test/Streamly/Test/Data/Fold.hs+5−5
@@ -25,7 +25,7 @@ import qualified Data.Map
2525import qualified Prelude
2626import qualified Streamly.Internal.Data.Array.Mut as MArray
2727import qualified Streamly.Internal.Data.Fold as Fold
28−import qualified Streamly.Internal.Data.Fold.Extra as Fold
28+import qualified Streamly.Internal.Data.Fold.Container as Fold
2929import qualified Streamly.Internal.Data.Stream as Stream
3030
3131import Prelude hiding
@@ -611,7 +611,7 @@ demux =
611611 , ("abc", 2)
612612 ] :: [(String, Int)])
613613 in Stream.fold
614− (Fold.demux table)
614+ (Fold.demuxKvToMap table)
615615 input
616616 `shouldReturn`
617617 Data.Map.fromList [("PRODUCT", 8),("SUM", 4),("abc",3),("xyz",2)]
@@ -628,7 +628,7 @@ demuxWith =
628628
629629 input = Stream.fromList [1, 2, 3, 4 :: Int]
630630 in Stream.fold
631− (Fold.demuxWith getKey (getFold . getKey))
631+ (Fold.demuxToContainer getKey (getFold . getKey))
632632 input
633633 `shouldReturn`
634634 Data.Map.fromList [("PRODUCT",3),("SUM",6)]
@@ -637,7 +637,7 @@ classifyWith :: Expectation
637637classifyWith =
638638 let input = Stream.fromList [("ONE",1),("ONE",1.1),("TWO",2), ("TWO",2.2)]
639639 in Stream.fold
640− (Fold.classifyWith fst (Fold.lmap snd Fold.toList))
640+ (Fold.toContainer fst (Fold.lmap snd Fold.toList))
641641 input
642642 `shouldReturn`
643643 Data.Map.fromList
@@ -654,7 +654,7 @@ classify =
654654 , ("TWO",(2, 2.2))
655655 ]
656656 in Stream.fold
657− (Fold.classify (Fold.lmap snd Fold.toList))
657+ (Fold.kvToMap (Fold.lmap snd Fold.toList))
658658 input
659659 `shouldReturn`
660660 Data.Map.fromList