on master help sign in
Change demux fold APIs and implementations
6b2f1c1Harendra Kumar
benchmark/Streamly/Benchmark/Data/Fold.hs+52−45
@@ -24,7 +24,6 @@ import System.Random (randomRIO)
24import Streamly.Prelude (SerialT)
24import Streamly.Prelude (SerialT)
25import Streamly.Internal.Data.Fold (Fold(..))
25import Streamly.Internal.Data.Fold (Fold(..))
26
26
27−import qualified Data.Map.Strict as Map
28import qualified Streamly.Internal.Data.Fold as FL
27import qualified Streamly.Internal.Data.Fold as FL
29import qualified Streamly.Internal.Data.Unfold as Unfold
28import qualified Streamly.Internal.Data.Unfold as Unfold
30import qualified Streamly.Internal.Data.Pipe as Pipe
29import qualified Streamly.Internal.Data.Pipe as Pipe
@@ -193,22 +192,29 @@ partitionByMinM =
193 IP.fold (FL.partitionByMinM (return . oddEven) FL.sum FL.length)
192 IP.fold (FL.partitionByMinM (return . oddEven) FL.sum FL.length)
194
193
195{-# INLINE demuxWith #-}
194{-# INLINE demuxWith #-}
196−demuxWith ::
197− (Monad m, Ord k)
195+demuxWith :: (Monad m, Ord k) =>
198− => (a -> (k, a'))
196+ (a -> k) -> (k -> Fold m a b) -> SerialT m a -> m (Map k b)
199− -> Map k (Fold m a' b)
197+demuxWith f g = S.fold (FL.demuxWith f g)
198+
199+{-# INLINE demuxWithInt #-}
200+demuxWithInt :: Monad m =>
200− -> SerialT m a
201+ (a -> Int) -> (Int -> Fold m a b) -> SerialT m a -> m (IntMap b)
201− -> m (Map k b)
202−demuxWith f mp = S.fold (FL.demuxWith f mp)
202+demuxWithInt f g = S.fold (FL.demuxWith f g)
203−
203+
204−{-# INLINE demuxDefaultWith #-}
204+{-# INLINE demuxWithHash #-}
205−demuxDefaultWith ::
206− (Monad m, Ord k, Num b)
205+demuxWithHash :: (Monad m, Ord k, Hashable k) =>
207− => (a -> (k, b))
206+ (a -> k) -> (k -> Fold m a b) -> SerialT m a -> m (HashMap k b)
208− -> Map k (Fold m b b)
207+demuxWithHash f g = S.fold (FL.demuxWith f g)
208+
209+{-# INLINE demuxMutWith #-}
210+demuxMutWith :: (MonadIO m, Ord k) =>
209− -> SerialT m a
211+ (a -> k) -> (k -> Fold m a b) -> SerialT m a -> m (Map k b)
210− -> m (Map k b, b)
212+demuxMutWith f g = S.fold (FL.demuxMutWith f g)
211−demuxDefaultWith f mp = S.fold (FL.demuxDefaultWith f mp (FL.lmap snd FL.sum))
213+
214+{-# INLINE demuxMutWithHash #-}
215+demuxMutWithHash :: (MonadIO m, Ord k, Hashable k) =>
216+ (a -> k) -> (k -> Fold m a b) -> SerialT m a -> m (HashMap k b)
217+demuxMutWithHash f g = S.fold (FL.demuxMutWith f g)
212
218
213{-# INLINE classifyWith #-}
219{-# INLINE classifyWith #-}
214classifyWith ::
220classifyWith ::
@@ -220,17 +226,6 @@ classifyWithInt ::
220 (MonadIO m, Num a) => (a -> Int) -> SerialT m a -> m (IntMap a)
226 (MonadIO m, Num a) => (a -> Int) -> SerialT m a -> m (IntMap a)
221classifyWithInt f = S.fold (FL.classifyWith f FL.sum)
227classifyWithInt f = S.fold (FL.classifyWith f FL.sum)
222
228
223−{-# INLINE classifyScanWith #-}
224−classifyScanWith ::
225− forall m k a. (Monad m, Ord k, Num a) => (a -> k) -> SerialT m a -> m ()
226−classifyScanWith f =
227− S.drain . S.postscan (g f FL.sum)
228−
229− where
230− g =
231− FL.classifyScanWith ::
232− (a -> k) -> Fold m a b -> Fold m a (m (Map k b), Maybe (k, b))
233−
234{-# INLINE classifyMutWith #-}
229{-# INLINE classifyMutWith #-}
235classifyMutWith ::
230classifyMutWith ::
236 (MonadIO m, Ord k, Num a) => (a -> k) -> SerialT m a -> m (Map k a)
231 (MonadIO m, Ord k, Num a) => (a -> k) -> SerialT m a -> m (Map k a)
@@ -433,38 +428,50 @@ o_n_heap_serial value =
433 ]
428 ]
434 , bgroup "key-value"
429 , bgroup "key-value"
435 [
430 [
436− benchIOSink value "demuxDefaultWith (64 buckets) [sum, length] sum"
431+ benchIOSink value "demuxWith (64 buckets) [sum, length]"
437− $ demuxDefaultWith (getKey 64) mp
432+ $ demuxWith (getKey 64) getFold
433+ , benchIOSink value "demuxWithInt (64 buckets) [sum, length]"
434+ $ demuxWithInt (getKey 64) getFold
435+ , benchIOSink value "demuxWithHash (64 buckets) [sum, length]"
436+ $ demuxWithHash (getKey 64) getFold
437+ , benchIOSink value "demuxMutWith (64 buckets) [sum, length]"
438+ $ demuxMutWith (getKey 64) getFold
438− , benchIOSink value "demuxWith (64 buckets) [sum, length]"
439+ , benchIOSink value "demuxMutWithHash (64 buckets) [sum, length]"
439− $ demuxWith (getKey 64) mp
440+ $ demuxMutWithHash (getKey 64) getFold
441+
442+ -- classify: immutable
440 , benchIOSink value "classifyWith (64 buckets) sum"
443 , benchIOSink value "classifyWith (64 buckets) sum"
441− $ classifyWith (fst . getKey 64)
444+ $ classifyWith (getKey 64)
442 , benchIOSink value "classifyWithInt (64 buckets) sum"
445 , benchIOSink value "classifyWithInt (64 buckets) sum"
443− $ classifyWithInt (fst . getKey 64)
446+ $ classifyWithInt (getKey 64)
444− , benchIOSink value "classifyScanWith (64 buckets) sum"
447+
445− $ classifyScanWith (fst . getKey 64)
448+ -- classify: mutable cells
446 , benchIOSink value "classifyMutWith (single bucket) sum"
449 , benchIOSink value "classifyMutWith (single bucket) sum"
447− $ classifyMutWith (fst . getKey 1)
450+ $ classifyMutWith (getKey 1)
448 , benchIOSink value "classifyMutWith (64 buckets) sum"
451 , benchIOSink value "classifyMutWith (64 buckets) sum"
449− $ classifyMutWith (fst . getKey 64)
452+ $ classifyMutWith (getKey 64)
450 , benchIOSink value "classifyMutWith (max buckets) sum"
453 , benchIOSink value "classifyMutWith (max buckets) sum"
451− $ classifyMutWith (fst . getKey value)
454+ $ classifyMutWith (getKey value)
452 , benchIOSink value "classifyMutWithInt (64 buckets) sum"
455 , benchIOSink value "classifyMutWithInt (64 buckets) sum"
453− $ classifyMutWithInt (fst . getKey 64)
456+ $ classifyMutWithInt (getKey 64)
454 , benchIOSink value "classifyMutWithHash (single bucket) sum"
457 , benchIOSink value "classifyMutWithHash (single bucket) sum"
455− $ classifyMutWithHash (fst . getKey 1)
458+ $ classifyMutWithHash (getKey 1)
456 , benchIOSink value "classifyMutWithHash (64 buckets) sum"
459 , benchIOSink value "classifyMutWithHash (64 buckets) sum"
457− $ classifyMutWithHash (fst . getKey 64)
460+ $ classifyMutWithHash (getKey 64)
458 , benchIOSink value "classifyMutWithHash (max buckets) sum"
461 , benchIOSink value "classifyMutWithHash (max buckets) sum"
459− $ classifyMutWithHash (fst . getKey value)
462+ $ classifyMutWithHash (getKey value)
460 ]
463 ]
461 ]
464 ]
462
465
463 where
466 where
464
467
465− getKey buckets x = (x `mod` buckets, x)
468+ getKey buckets = (`mod` buckets)
466
469
470+ getFold k =
471+ case k of
472+ 0 -> FL.sum
473+ 1 -> FL.length
467− mp = Map.fromList [(0, FL.sum), (1, FL.length)]
474+ _ -> FL.length
468
475
469-------------------------------------------------------------------------------
476-------------------------------------------------------------------------------
470-- Driver
477-- Driver
core/src/Streamly/Internal/Data/Fold.hs+197−177
@@ -214,14 +214,18 @@ module Streamly.Internal.Data.Fold
214
214
215 -- ** Demultiplexing
215 -- ** Demultiplexing
216 -- | Direct values in the input stream to different folds using an n-ary
216 -- | Direct values in the input stream to different folds using an n-ary
217− -- fold selector. 'demux' is a generalization of 'classify' where each key
217+ -- fold selector. 'demux' is a generalization of 'classify' (and
218− -- of the classifier can use a different fold.
218+ -- 'partition') where each key of the classifier can use a different fold.
219− , demux -- XXX rename this to demux_
219+ , demux
220 , demuxWith
220 , demuxWith
221− , demuxDefault -- XXX rename this to demux
221+ , demuxScanWith
222− , demuxDefaultWith
222+ , demuxScanMutWith
223− -- , demuxWithSel
223+ , demuxMutWith
224+
225+ -- TODO: These can be implemented using the above operations
226+ -- , demuxWithSel -- Stop when the fold for the specified key stops
224− -- , demuxWithMin
227+ -- , demuxWithMin -- Stop when any of the folds stop
228+ -- , demuxWithAll -- Stop when all the folds stop (run once)
225
229
226 -- ** Classifying
230 -- ** Classifying
227 -- | In an input stream of key value pairs fold values for different keys
231 -- | In an input stream of key value pairs fold values for different keys
@@ -281,8 +285,6 @@ import Data.Int (Int64)
281import Data.IORef (newIORef, readIORef, writeIORef)
285import Data.IORef (newIORef, readIORef, writeIORef)
282import Data.Map.Strict (Map)
286import Data.Map.Strict (Map)
283import Data.Maybe (isJust, fromJust)
287import Data.Maybe (isJust, fromJust)
284−import Streamly.Internal.Data.Either.Strict
285− (Either'(..), fromLeft', fromRight', isLeft', isRight')
286import Streamly.Internal.Data.IsMap (IsMap(..))
288import Streamly.Internal.Data.IsMap (IsMap(..))
287import Streamly.Internal.Data.Pipe.Type (Pipe (..), PipeState(..))
289import Streamly.Internal.Data.Pipe.Type (Pipe (..), PipeState(..))
288import Streamly.Internal.Data.Unfold.Type (Unfold(..))
290import Streamly.Internal.Data.Unfold.Type (Unfold(..))
@@ -290,7 +292,6 @@ import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))
290import Streamly.Internal.Data.Stream.Serial (SerialT(..))
292import Streamly.Internal.Data.Stream.Serial (SerialT(..))
291
293
292import qualified Data.IntSet as IntSet
294import qualified Data.IntSet as IntSet
293−import qualified Data.Map.Strict as Map
294import qualified Data.Set as Set
295import qualified Data.Set as Set
295import qualified Streamly.Internal.Data.IsMap as IsMap
296import qualified Streamly.Internal.Data.IsMap as IsMap
296import qualified Prelude
297import qualified Prelude
@@ -1467,201 +1468,213 @@ partition = partitionBy id
1467-- partitionN fs = Fold step begin done
1468-- partitionN fs = Fold step begin done
1468-}
1469-}
1469
1470
1471+------------------------------------------------------------------------------
1472+-- demux: in a key value stream fold each key sub-stream with a different fold
1473+------------------------------------------------------------------------------
1474+
1470-- TODO Demultiplex an input element into a number of typed variants. We want
1475-- TODO Demultiplex an input element into a number of typed variants. We want
1471-- to statically restrict the target values within a set of predefined types,
1476-- to statically restrict the target values within a set of predefined types,
1472−-- an enumeration of a GADT. We also want to make sure that the Map contains
1477+-- an enumeration of a GADT.
1473−-- only those types and the full set of those types.
1474−--
1475−-- TODO Instead of the input Map it should probably be a lookup-table using an
1476−-- array and not in GC memory. The same applies to the output Map as well.
1477−-- However, that would only be helpful if we have a very large data structure,
1478−-- need to measure and see how it scales.
1479--
1478--
1480-- This is the consumer side dual of the producer side 'mux' operation (XXX to
1479-- This is the consumer side dual of the producer side 'mux' operation (XXX to
1481-- be implemented).
1480-- be implemented).
1481+--
1482+-- XXX If we use Refold in it, it can perhaps fuse/be more efficient. For
1483+-- example we can store just the result rather than storing the whole fold in
1484+-- the Map.
1482
1485
1483−-- | Split the input stream based on a key field and fold each split using a
1486+-- | In a key value stream, fold values corresponding to each key with a key
1484−-- specific fold collecting the results in a map from the keys to the results.
1487+-- specific fold. The fold returns the fold result as the second component of
1485−-- Useful for cases like protocol handlers to handle different type of packets
1488+-- the output tuple whenever a fold terminates. The first component of the
1486−-- using different handlers.
1489+-- tuple is a container of in-progress folds. If a fold terminates, another
1490+-- instance of the fold is started upon receiving an input with that key.
1487--
1491--
1488−-- @
1492+-- This can be used to scan a stream and collect the results from the scan
1493+-- output.
1489--
1494--
1490−-- |-------Fold m a b
1495+-- /Pre-release/
1491−-- -----stream m a-----Map-----|
1492−-- |-------Fold m a b
1493−-- |
1494−-- ...
1495−-- @
1496--
1496--
1497−-- Any input that does not map to a fold in the input Map is silently ignored.
1497+{-# INLINE demuxScanWith #-}
1498+demuxScanWith :: (Monad m, IsMap f, Traversable f) =>
1499+ (a -> Key f)
1500+ -> (Key f -> Fold m a b)
1501+ -> Fold m a (m (f b), Maybe (Key f, b))
1502+demuxScanWith getKey getFold = fmap extract $ foldlM' step initial
1503+
1504+ where
1505+
1506+ initial = return $ Tuple' IsMap.mapEmpty Nothing
1507+
1508+ {-# INLINE runFold #-}
1509+ runFold kv (Fold step1 initial1 extract1) (k, a) = do
1510+ res <- initial1
1511+ case res of
1512+ Partial s -> do
1513+ res1 <- step1 s a
1514+ return
1515+ $ case res1 of
1516+ Partial _ ->
1517+ let fld = Fold step1 (return res1) extract1
1518+ in Tuple' (IsMap.mapInsert k fld kv) Nothing
1519+ Done b -> Tuple' (IsMap.mapDelete k kv) (Just (k, b))
1520+ Done b -> return $ Tuple' kv (Just (k, b))
1521+
1522+ step (Tuple' kv _) a = do
1523+ let k = getKey a
1524+ let fld = getFold k
1525+ case IsMap.mapLookup k kv of
1526+ Nothing -> runFold kv fld (k, a)
1527+ Just f -> runFold kv f (k, a)
1528+
1529+ extract (Tuple' kv x) = (Prelude.mapM f kv, x)
1530+
1531+ where
1532+
1533+ f (Fold _ i e) = do
1534+ r <- i
1535+ case r of
1536+ Partial s -> e s
1537+ Done b -> return b
1538+
1539+-- | This is specialized version of 'demuxScanWith' that uses mutable cells as
1540+-- fold accumulators for better performance.
1498--
1541--
1499−-- >>> demuxWith f kv = fmap fst $ Fold.demuxDefaultWith f kv Fold.drain
1542+-- >>> demuxScanMutWith = Fold.demuxScanWith
1500--
1543--
1501−-- /Pre-release/
1544+{-# INLINE demuxScanMutWith #-}
1545+demuxScanMutWith :: (MonadIO m, IsMap f, Traversable f) =>
1546+ (a -> Key f)
1547+ -> (Key f -> Fold m a b)
1548+ -> Fold m a (m (f b), Maybe (Key f, b))
1549+demuxScanMutWith getKey getFold = fmap extract $ foldlM' step initial
1550+
1551+ where
1552+
1553+ initial = return $ Tuple' IsMap.mapEmpty Nothing
1554+
1555+ {-# INLINE initFold #-}
1556+ initFold kv (Fold step1 initial1 extract1) (k, a) = do
1557+ res <- initial1
1558+ case res of
1559+ Partial s -> do
1560+ res1 <- step1 s a
1561+ case res1 of
1562+ Partial _ -> do
1563+ let fld = Fold step1 (return res1) extract1
1564+ ref <- liftIO $ newIORef fld
1565+ return $ Tuple' (IsMap.mapInsert k ref kv) Nothing
1566+ Done b -> return $ Tuple' kv (Just (k, b))
1567+ Done b -> return $ Tuple' kv (Just (k, b))
1568+
1569+ {-# INLINE runFold #-}
1570+ runFold kv ref (Fold step1 initial1 extract1) (k, a) = do
1571+ res <- initial1
1572+ case res of
1573+ Partial s -> do
1574+ res1 <- step1 s a
1575+ case res1 of
1576+ Partial _ -> do
1577+ let fld = Fold step1 (return res1) extract1
1578+ liftIO $ writeIORef ref fld
1579+ return $ Tuple' kv Nothing
1580+ Done b ->
1581+ let kv1 = IsMap.mapDelete k kv
1582+ in return $ Tuple' kv1 (Just (k, b))
1583+ Done _ -> error "demuxScanMutWith: unreachable"
1584+
1585+ step (Tuple' kv _) a = do
1586+ let k = getKey a
1587+ case IsMap.mapLookup k kv of
1588+ Nothing -> do
1589+ let f = getFold k
1590+ initFold kv f (k, a)
1591+ Just ref -> do
1592+ f <- liftIO $ readIORef ref
1593+ runFold kv ref f (k, a)
1594+
1595+ extract (Tuple' kv x) = (Prelude.mapM f kv, x)
1596+
1597+ where
1598+
1599+ f ref = do
1600+ (Fold _ i e) <- liftIO $ readIORef ref
1601+ r <- i
1602+ case r of
1603+ Partial s -> e s
1604+ Done b -> return b
1605+
1606+-- | This collects all the results of 'demuxScanWith' in a container.
1502--
1607--
1503{-# INLINE demuxWith #-}
1608{-# INLINE demuxWith #-}
1504−demuxWith :: (Monad m, Ord k)
1609+demuxWith :: (Monad m, IsMap f, Traversable f) =>
1610+ (a -> Key f) -> (Key f -> Fold m a b) -> Fold m a (f b)
1611+demuxWith getKey getFold =
1612+ let
1613+ classifier = demuxScanWith getKey getFold
1614+ getMap Nothing = pure IsMap.mapEmpty
1615+ getMap (Just action) = action
1616+ aggregator =
1617+ teeWith IsMap.mapUnion
1618+ (rmapM getMap $ lmap fst last)
1619+ (lmap snd $ catMaybes toMap)
1620+ in postscan classifier aggregator
1621+
1622+-- | Same as 'demuxWith' but uses 'demuxScanMutWith' for better performance.
1623+--
1624+-- >>> demuxMutWith = Fold.demuxWith
1625+--
1626+{-# INLINE demuxMutWith #-}
1627+demuxMutWith :: (MonadIO m, IsMap f, Traversable f) =>
1505− => (a -> (k, a')) -> Map k (Fold m a' b) -> Fold m a (Map k b)
1628+ (a -> Key f) -> (Key f -> Fold m a b) -> Fold m a (f b)
1506−demuxWith f kv = fmap fst $ demuxDefaultWith f kv drain
1629+demuxMutWith getKey getFold =
1630+ let
1631+ classifier = demuxScanMutWith getKey getFold
1632+ getMap Nothing = pure IsMap.mapEmpty
1633+ getMap (Just action) = action
1634+ aggregator =
1635+ teeWith IsMap.mapUnion
1636+ (rmapM getMap $ lmap fst last)
1637+ (lmap snd $ catMaybes toMap)
1638+ in postscan classifier aggregator
1507
1639
1508−-- | Fold a stream of key value pairs using a map of specific folds for each
1640+-- | Fold a stream of key value pairs using a function that maps keys to folds.
1509−-- key into a map from keys to the results of fold outputs of the corresponding
1510−-- values.
1511--
1641--
1512−-- >>> import qualified Data.Map
1642+-- >>> import Data.Map (Map)
1513-- >>> :{
1643-- >>> :{
1514−-- let table = Data.Map.fromList [("SUM", Fold.sum), ("PRODUCT", Fold.product)]
1644+-- let f "SUM" = Fold.sum
1645+-- f _ = Fold.product
1515-- input = Stream.fromList [("SUM",1),("PRODUCT",2),("SUM",3),("PRODUCT",4)]
1646-- input = Stream.fromList [("SUM",1),("PRODUCT",2),("SUM",3),("PRODUCT",4)]
1516−-- in Stream.fold (Fold.demux table) input
1647+-- in Stream.fold (Fold.demux f) input :: IO (Map String Int)
1517-- :}
1648-- :}
1518-- fromList [("PRODUCT",8),("SUM",4)]
1649-- fromList [("PRODUCT",8),("SUM",4)]
1519--
1650--
1520−-- >>> demux = Fold.demuxWith id
1651+-- >>> demux f = Fold.demuxWith fst (Fold.lmap snd . f)
1521--
1652--
1522-- /Pre-release/
1653-- /Pre-release/
1523{-# INLINE demux #-}
1654{-# INLINE demux #-}
1524−demux :: (Monad m, Ord k)
1655+demux :: (Monad m, IsMap f, Traversable f) =>
1525− => Map k (Fold m a b) -> Fold m (k, a) (Map k b)
1526−demux = demuxWith id
1527−
1528−data DemuxState s b doneMap runMap =
1529− DemuxMapAndDefault !s !doneMap !runMap
1530− | DemuxOnlyMap b !doneMap !runMap
1531− | DemuxOnlyDefault s !doneMap
1532−
1533−-- | Like 'demuxWith' but uses a default catchall fold to handle inputs which
1534−-- do not have a specific fold in the map to handle them.
1535−--
1536−-- If any fold in the map stops, inputs meant for that fold are sent to the
1537−-- catchall fold. If the catchall fold stops then inputs that do not match any
1538−-- fold are ignored.
1539−--
1540−-- Stops when all the folds, including the catchall fold, stop.
1541−--
1542−-- /Pre-release/
1543−--
1544−{-# INLINE demuxDefaultWith #-}
1545−demuxDefaultWith :: (Monad m, Ord k)
1546− => (a -> (k, a'))
1547− -> Map k (Fold m a' b)
1656+ (Key f -> Fold m a b) -> Fold m (Key f, a) (f b)
1548− -> Fold m (k, a') c
1657+demux f = demuxWith fst (lmap snd . f)
1549− -> Fold m a (Map k b, c)
1550−demuxDefaultWith f kv (Fold dstep dinitial dextract) =
1551− Fold step initial extract
1552
1658
1553− where
1659+------------------------------------------------------------------------------
1554−
1660+-- Classify: Like demux but uses the same fold for all keys.
1555− initial = do
1661+------------------------------------------------------------------------------
1556− let runInit (Fold step1 initial1 done1) = do
1557− r <- initial1
1558− return
1559− $ case r of
1560− Partial _ -> Right' (Fold step1 (return r) done1)
1561− Done b -> Left' b
1562−
1563− -- initialize folds in the kv map and separate the ones that are done
1564− -- from running ones
1565− kv1 <- Prelude.mapM runInit kv
1566− let runMap = Map.map fromRight' $ Map.filter isRight' kv1
1567− doneMap = Map.map fromLeft' $ Map.filter isLeft' kv1
1568−
1569− -- Run the default fold, and decide the next state based on its result
1570− dres <- dinitial
1571− return
1572− $ case dres of
1573− Partial s ->
1574− Partial
1575− $ if Map.size runMap > 0
1576− then DemuxMapAndDefault s doneMap runMap
1577− else DemuxOnlyDefault s doneMap
1578− Done b ->
1579− if Map.size runMap > 0
1580− then Partial $ DemuxOnlyMap b doneMap runMap
1581− else Done (doneMap, b)
1582
1662
1583− {-# INLINE runFold #-}
1663+-- XXX Change these to make the behavior similar to demux* variants. We can
1584− runFold fPartial fDone doneMap runMap (Fold step1 initial1 done1) k a1 = do
1664+-- implement this using classifyScanManyWith. Maintain a set of done folds in
1585− resi <- initial1
1665+-- the underlying monad, and when initial is called look it up, if the fold is
1586− case resi of
1666+-- done then initial would set a flag in the state to ignore the input or
1587− Partial st -> do
1667+-- return an error.
1588− res <- step1 st a1
1589− return $ case res of
1590− Partial s ->
1591− let fld = Fold step1 (return $ Partial s) done1
1592− runMap1 = Map.insert k fld runMap
1593− in Partial $ fPartial doneMap runMap1
1594− Done b -> do
1595− let runMap1 = Map.delete k runMap
1596− doneMap1 = Map.insert k b doneMap
1597− if Map.size runMap1 == 0
1598− then fDone doneMap1
1599− else Partial $ fPartial doneMap1 runMap1
1600− Done _ -> error "Bug: demuxDefaultWith: Done fold"
1601−
1602− step (DemuxMapAndDefault dacc doneMap runMap) a = do
1603− let (k, a1) = f a
1604− case Map.lookup k runMap of
1605− Nothing -> do
1606− res <- dstep dacc (k, a1)
1607− return
1608− $ Partial
1609− $ case res of
1610− Partial s -> DemuxMapAndDefault s doneMap runMap
1611− Done b -> DemuxOnlyMap b doneMap runMap
1612− Just fld ->
1613− runFold
1614− (DemuxMapAndDefault dacc)
1615− (Partial . DemuxOnlyDefault dacc)
1616− doneMap runMap fld k a1
1617−
1618− step (DemuxOnlyMap dval doneMap runMap) a = do
1619− let (k, a1) = f a
1620− case Map.lookup k runMap of
1621− Nothing -> return $ Partial $ DemuxOnlyMap dval doneMap runMap
1622− Just fld ->
1623− runFold
1624− (DemuxOnlyMap dval)
1625− (Done . (, dval))
1626− doneMap runMap fld k a1
1627− step (DemuxOnlyDefault dacc doneMap) a = do
1628− let (k, a1) = f a
1629− res <- dstep dacc (k, a1)
1630− return
1631− $ case res of
1632− Partial s -> Partial $ DemuxOnlyDefault s doneMap
1633− Done b -> Done (doneMap, b)
1634−
1635− runExtract (Fold _ initial1 done1) = do
1636− res <- initial1
1637− case res of
1638− Partial s -> done1 s
1639− Done b -> return b
1640−
1641− extract (DemuxMapAndDefault dacc doneMap runMap) = do
1642− b <- dextract dacc
1643− runMap1 <- Prelude.mapM runExtract runMap
1644− return (doneMap `Map.union` runMap1, b)
1645− extract (DemuxOnlyMap dval doneMap runMap) = do
1646− runMap1 <- Prelude.mapM runExtract runMap
1647− return (doneMap `Map.union` runMap1, dval)
1648− extract (DemuxOnlyDefault dacc doneMap) = do
1649− b <- dextract dacc
1650− return (doneMap, b)
1651−
1652−-- |
1653−-- >>> demuxDefault = Fold.demuxDefaultWith id
1654−--
1655−-- /Pre-release/
1656−{-# INLINE demuxDefault #-}
1657−demuxDefault :: (Monad m, Ord k)
1658− => Map k (Fold m a b) -> Fold m (k, a) b -> Fold m (k, a) (Map k b, b)
1659−demuxDefault = demuxDefaultWith id
1660
1668
1661-- | Folds the values for each key using the supplied fold. When scanning, as
1669-- | Folds the values for each key using the supplied fold. When scanning, as
1662-- soon as the fold is complete, its result is available in the second
1670-- soon as the fold is complete, its result is available in the second
1663-- component of the tuple. The first component of the tuple is a snapshot of
1671-- component of the tuple. The first component of the tuple is a snapshot of
1664-- the in-progress folds.
1672-- the in-progress folds.
1673+--
1674+-- Once the fold for a key is done, any future values of the key are ignored.
1675+--
1676+-- >>> classifyScanWith f fld = Fold.demuxScanWith f (const fld)
1677+--
1665{-# INLINE classifyScanWith #-}
1678{-# INLINE classifyScanWith #-}
1666classifyScanWith :: (Monad m, IsMap f, Traversable f, Ord (Key f)) =>
1679classifyScanWith :: (Monad m, IsMap f, Traversable f, Ord (Key f)) =>
1667 -- Note: we need to return the Map itself to display the in-progress values
1680 -- Note: we need to return the Map itself to display the in-progress values
@@ -1716,6 +1729,10 @@ classifyScanWith f (Fold step1 initial1 extract1) =
1716-- | Same as classifyScanWith except that it uses mutable IORef cells in the
1729-- | Same as classifyScanWith except that it uses mutable IORef cells in the
1717-- Map providing better performance. Be aware that if this is used as a scan,
1730-- Map providing better performance. Be aware that if this is used as a scan,
1718-- the values in the intermediate Maps would be mutable.
1731-- the values in the intermediate Maps would be mutable.
1732+--
1733+-- >>> classifyScanMutWith = classifyScanWith
1734+-- >>> classifyScanMutWith f fld = demuxScanMutWith f (const fld)
1735+--
1719{-# INLINE classifyScanMutWith #-}
1736{-# INLINE classifyScanMutWith #-}
1720classifyScanMutWith :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>
1737classifyScanMutWith :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>
1721 (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))
1738 (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))
@@ -1805,8 +1822,11 @@ classifyWith f fld =
1805 (lmap snd $ catMaybes toMap)
1822 (lmap snd $ catMaybes toMap)
1806 in postscan classifier aggregator
1823 in postscan classifier aggregator
1807
1824
1808−-- | Like classifyWith but maybe faster because it uses mutable cells as fold
1825+-- | Same as 'classifyWith' but maybe faster because it uses mutable cells as
1809−-- accumulators in the Map.
1826+-- fold accumulators in the Map.
1827+--
1828+-- >>> classifyMutWith = Fold.classifyWith
1829+--
1810{-# INLINE classifyMutWith #-}
1830{-# INLINE classifyMutWith #-}
1811classifyMutWith :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>
1831classifyMutWith :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>
1812 (a -> Key f) -> Fold m a b -> Fold m a (f b)
1832 (a -> Key f) -> Fold m a b -> Fold m a (f b)
test/Streamly/Test/Data/Fold.hs+20−71
@@ -558,88 +558,42 @@ headAndRest ls = monadicIO $ do
558
558
559demux :: Expectation
559demux :: Expectation
560demux =
560demux =
561− let table = Data.Map.fromList [("SUM", FL.sum), ("PRODUCT", FL.product)]
561+ let table "SUM" = FL.sum
562+ table "PRODUCT" = FL.product
563+ table _ = FL.length
562 input = Stream.fromList (
564 input = Stream.fromList (
563 [ ("SUM", 1)
565 [ ("SUM", 1)
566+ , ("abc", 1)
564 , ("PRODUCT", 2)
567 , ("PRODUCT", 2)
568+ , ("abc", 2)
565 , ("SUM",3)
569 , ("SUM",3)
570+ , ("xyz", 1)
566 , ("PRODUCT", 4)
571 , ("PRODUCT", 4)
572+ , ("xyz", 2)
573+ , ("abc", 2)
567 ] :: [(String, Int)])
574 ] :: [(String, Int)])
568 in Stream.fold
575 in Stream.fold
569 (F.demux table)
576 (F.demux table)
570 input
577 input
571 `shouldReturn`
578 `shouldReturn`
572− Data.Map.fromList [("PRODUCT", 8),("SUM", 4)]
579+ Data.Map.fromList [("PRODUCT", 8),("SUM", 4),("abc",3),("xyz",2)]
573
580
574
581
575−demuxWithSum :: Expectation
576−demuxWithSum =
577− let f x = ("SUM", x::Int)
578− table = Data.Map.fromList [("SUM", FL.sum)]
579− input = Stream.fromList [1, 4]
580− in Stream.fold
581− (F.demuxWith f table)
582+demuxWith :: Expectation
582− input
583− `shouldReturn`
584− Data.Map.fromList [("SUM", 5)]
585−
586−demuxWithProduct :: Expectation
587−demuxWithProduct =
588− let f x = ("PRODUCT", x::Int)
589− table = Data.Map.fromList [("PRODUCT", FL.product)]
590− input = Stream.fromList [2, 4]
591− in Stream.fold
592− (F.demuxWith f table)
583+demuxWith =
593− input
584+ let getKey x | even x = "SUM"
594− `shouldReturn`
585+ | otherwise = "PRODUCT"
595− Data.Map.fromList [("PRODUCT", 8)]
596−
597−demuxDefaultWithSum :: Expectation
598−demuxDefaultWithSum =
599− let f x = ("SUM", x::Int)
600− table = Data.Map.fromList [("SUM", FL.sum)]
601− input = Stream.fromList [2, 4]
602− in Stream.fold
603− (F.demuxDefaultWith f table (FL.lmap snd FL.sum))
604− input
605− `shouldReturn`
606− (Data.Map.fromList [("SUM" , 6)] , 0)
607
586
608−demuxDefaultWithProduct :: Expectation
587+ getFold "SUM" = FL.sum
609−demuxDefaultWithProduct =
610− let f x = ("PRODUCT", x::Int)
611− table = Data.Map.fromList [("PRODUCT", FL.product)]
612− input = Stream.fromList [2, 4]
613− in Stream.fold
614− (F.demuxDefaultWith f table (FL.lmap snd FL.product))
615− input
616− `shouldReturn`
617− (Data.Map.fromList [("PRODUCT" , 8)] , 1)
618−
619−demuxDefault :: Expectation
620−demuxDefault =
621− let table = Data.Map.fromList [("SUM", FL.sum), ("PRODUCT", FL.product)]
622− input = Stream.fromList
623− [ ("SUM", 1::Int)
624− , ("PRODUCT", 2::Int)
625− , ("SUM",3)
626− , ("PRODUCT", 4::Int)
588+ getFold "PRODUCT" = FL.product
627− ]
589+ getFold _ = error "demuxWith: bug"
628− in Stream.fold
629− (F.demuxDefault table (FL.lmap snd FL.product))
630− input
631− `shouldReturn`
632− (Data.Map.fromList [("PRODUCT", 8), ("SUM", 4)], 1)
633
590
634−demuxDefaultEmpty :: Expectation
635−demuxDefaultEmpty =
636− let table = Data.Map.empty
637− input = Stream.fromList []
591+ input = Stream.fromList [1, 2, 3, 4 :: Int]
638 in Stream.fold
592 in Stream.fold
639− (F.demuxDefault table (FL.lmap snd FL.product))
593+ (F.demuxWith getKey getFold)
640 input
594 input
641 `shouldReturn`
595 `shouldReturn`
642− (Data.Map.fromList ([]::[(String, Int)]), 1)
596+ Data.Map.fromList [("PRODUCT",3),("SUM",6)]
643
597
644classifyWith :: Expectation
598classifyWith :: Expectation
645classifyWith =
599classifyWith =
@@ -716,12 +670,7 @@ main = hspec $ do
716 prop "toList" toList
670 prop "toList" toList
717 prop "toListRev" toListRev
671 prop "toListRev" toListRev
718 prop "demux" demux
672 prop "demux" demux
719− prop "demuxWithSum" demuxWithSum
673+ prop "demuxWith" demuxWith
720− prop "demuxWithProduct" demuxWithProduct
721− prop "demuxDefaultWithSum" demuxDefaultWithSum
722− prop "demuxDefaultWithProduct" demuxDefaultWithProduct
723− prop "demuxDefault" demuxDefault
724− prop "demuxDefaultEmpty" demuxDefaultEmpty
725 prop "classifyWith" classifyWith
674 prop "classifyWith" classifyWith
726 prop "classify" classify
675 prop "classify" classify
727
676