on master help sign in
Fix foldMany documentation and behavior

* Run the fold only when the stream is non-empty

commitf593770eb11a856797cdd9a6db4dad4ed44e01bd authorHarendra Kumar <harendra@composewell.com> dateFri 13 May 2022, 22:22 (4 years ago) parentc89ce59
3 files changed+66−67
core/src/Streamly/Internal/Data/Stream/Bottom.hs+25−8 core/src/Streamly/Internal/Data/Stream/Reduce.hs+19−17 core/src/Streamly/Internal/Data/Stream/StreamD/Type.hs+22−42
core/src/Streamly/Internal/Data/Stream/Bottom.hs+25−8
@@ -211,9 +211,10 @@ build f = fold (Fold.duplicate f)
211211-- Definitions:
212212--
213213-- >>> fold f = fmap fst . Stream.foldBreak f
214+-- >>> fold f = Stream.parse (Parser.fromFold f)
215+-- >>> fold f = Stream.fold Fold.one . Stream.foldManyPost f
214216-- >>> fold f = Fold.extractM . Stream.buildl f
215217-- >>> fold f = Fold.extractM <=< Stream.build f
216−-- >>> fold f = Stream.parse (Parser.fromFold f)
217218--
218219-- Example:
219220--
@@ -594,16 +595,32 @@ concatMap f m = fromStreamD $ D.concatMap (toStreamD . f) (toStreamD m)
594595concatM :: Monad m => m (Stream m a) -> Stream m a
595596concatM generator = concatMapM (\() -> generator) (fromPure ())
596597
597−-- | Like 'foldMany' but appends empty fold output if the fold and stream
598−-- termination aligns:
598+-- XXX Need a more intuitive name, and need to reconcile the names
599+-- foldMany/fold/parse/parseMany/parseManyPost etc.
600+
601+-- | Like 'foldMany' but evaluates the fold before the stream, and yields its
602+-- output even if the stream is empty, therefore, always results in a non-empty
603+-- output even on an empty stream (default result of the fold).
604+--
605+-- Example, empty stream:
599606--
600607-- >>> f = Fold.take 2 Fold.sum
601−-- >>> Stream.fold Fold.toList $ Stream.foldManyPost f $ Stream.fromList []
608+-- >>> fmany = Stream.fold Fold.toList . Stream.foldManyPost f
609+-- >>> fmany $ Stream.fromList []
602610-- [0]
603−-- >>> Stream.fold Fold.toList $ Stream.foldManyPost f $ Stream.fromList [1..9]
604−-- [3,7,11,15,9]
605−-- >>> Stream.fold Fold.toList $ Stream.foldManyPost f $ Stream.fromList [1..10]
606−-- [3,7,11,15,19,0]
611+--
612+-- Example, last fold empty:
613+--
614+-- >>> fmany $ Stream.fromList [1..4]
615+-- [3,7,0]
616+--
617+-- Example, last fold non-empty:
618+--
619+-- >>> fmany $ Stream.fromList [1..5]
620+-- [3,7,5]
621+--
622+-- Note that using a closed fold e.g. @Fold.take 0@, would result in an
623+-- infinite stream without consuming the input.
607624--
608625-- /Pre-release/
609626--
core/src/Streamly/Internal/Data/Stream/Reduce.hs+19−17
@@ -125,31 +125,33 @@ dropSuffix = error "Not implemented yet!"
125125-- Folding
126126------------------------------------------------------------------------------
127127
128−-- Splitting operations that take a predicate and a Fold can be
129−-- expressed using parseMany. Operations like chunksOf, intervalsOf, split*,
130−-- can be expressed using parseMany when used with an appropriate Parser.
128+-- | Apply a 'Fold' repeatedly on a stream and emit the results in the
129+-- output stream. Unlike 'foldManyPost' it evaluates the fold after the stream,
130+-- therefore, an empty input stream results in an empty output stream.
131131--
132−-- XXX We need takeGE/takeBetween to implement "some" using "many".
133−
134−-- | Apply a 'Fold' repeatedly on a stream and emit the fold outputs in the
135−-- output stream.
132+-- Definition:
136133--
137−-- To sum every two contiguous elements in a stream:
134+-- >>> foldMany f = Stream.parseMany (Parser.fromFold f)
135+--
136+-- Example, empty stream:
138137--
139138-- >>> f = Fold.take 2 Fold.sum
140−-- >>> Stream.fold Fold.toList $ Stream.foldMany f $ Stream.fromList [1..10]
141−-- [3,7,11,15,19]
139+-- >>> fmany = Stream.fold Fold.toList . Stream.foldMany f
140+-- >>> fmany $ Stream.fromList []
141+-- []
142142--
143−-- On an empty stream the output is empty:
143+-- Example, last fold empty:
144144--
145−-- >>> Stream.fold Fold.toList $ Stream.foldMany f $ Stream.fromList []
146−-- []
145+-- >>> fmany $ Stream.fromList [1..4]
146+-- [3,7]
147+--
148+-- Example, last fold non-empty:
147149--
148−-- Note @Stream.foldMany (Fold.take 0)@ would result in an infinite loop in a
149−-- non-empty stream.
150+-- >>> fmany $ Stream.fromList [1..5]
151+-- [3,7,5]
150152--
151−-- Note 'foldMany' on an empty stream results in an empty stream. Therefore,
152−-- @Stream.fold f@ is not the same as @Stream.head . Stream.foldMany f@.
153+-- Note that using a closed fold e.g. @Fold.take 0@, would result in an
154+-- infinite stream on a non-empty input stream.
153155--
154156{-# INLINE foldMany #-}
155157foldMany
core/src/Streamly/Internal/Data/Stream/StreamD/Type.hs+22−42
@@ -895,14 +895,7 @@ data FoldManyPost s fs b a
895895 | FoldManyPostYield b (FoldManyPost s fs b a)
896896 | FoldManyPostDone
897897
898−-- | Like foldMany but with the following differences:
899−--
900−-- * If the stream is empty the default value of the fold would still be
901−-- emitted in the output.
902−-- * At the end of the stream if the last application of the fold did not
903−-- receive any input it would still yield the default fold accumulator as the
904−-- last value.
905−--
898+-- | 'Streamly.Internal.Data.Stream.foldManyPost'.
906899{-# INLINE_NORMAL foldManyPost #-}
907900foldManyPost :: Monad m => Fold m a b -> Stream m a -> Stream m b
908901foldManyPost (Fold fstep initial extract) (Stream step state) =
@@ -941,23 +934,14 @@ foldManyPost (Fold fstep initial extract) (Stream step state) =
941934{-# ANN type FoldMany Fuse #-}
942935data FoldMany s fs b a
943936 = FoldManyStart s
944− | FoldManyFirst fs s
937+ | FoldManyFirst a s
945938 | FoldManyLoop s fs
946939 | FoldManyYield b (FoldMany s fs b a)
947940 | FoldManyDone
948941
949942-- XXX Nested foldMany does not fuse.
950−--
951−-- | Apply a fold multiple times until the stream ends. If the stream is empty
952−-- the output would be empty.
953−--
954−-- @foldMany f = parseMany (fromFold f)@
955−--
956−-- A terminating fold may terminate even without accepting a single input. So
957−-- we run the fold's initial action before evaluating the stream. However, this
958−-- means that if later the stream does not yield anything we have to discard
959−-- the fold's initial result which could have generated an effect.
960−--
943+
944+-- | 'Streamly.Internal.Data.Stream.foldMany'.
961945{-# INLINE_NORMAL foldMany #-}
962946foldMany :: Monad m => Fold m a b -> Stream m a -> Stream m b
963947foldMany (Fold fstep initial extract) (Stream step state) =
@@ -975,19 +959,17 @@ foldMany (Fold fstep initial extract) (Stream step state) =
975959 FL.Partial ps -> FoldManyLoop s ps
976960
977961 {-# INLINE_LATE step' #-}
978− step' _ (FoldManyStart st) = do
979− r <- initial
980− return
981− $ Skip
982− $ case r of
983− FL.Done b -> FoldManyYield b (FoldManyStart st)
984− FL.Partial fs -> FoldManyFirst fs st
985− step' gst (FoldManyFirst fs st) = do
962+ step' gst (FoldManyStart st) = do
986963 r <- step (adaptState gst) st
987964 case r of
988− Yield x s -> consume x s fs
989− Skip s -> return $ Skip (FoldManyFirst fs s)
965+ Yield x s -> return $ Skip (FoldManyFirst x s)
966+ Skip s -> return $ Skip (FoldManyStart s)
990967 Stop -> return Stop
968+ step' _ (FoldManyFirst x st) = do
969+ r <- initial
970+ case r of
971+ FL.Done b -> return $ Skip $ FoldManyYield b (FoldManyFirst x st)
972+ FL.Partial fs -> consume x st fs
991973 step' gst (FoldManyLoop st fs) = do
992974 r <- step (adaptState gst) st
993975 case r of
@@ -1003,8 +985,8 @@ foldMany (Fold fstep initial extract) (Stream step state) =
1003985chunksOf :: Monad m => Int -> Fold m a b -> Stream m a -> Stream m b
1004986chunksOf n f = foldMany (FL.take n f)
1005987
1006−-- Keep the argument order consistent with consumeIterateM.
1007−--
988+-- Keep the argument order consistent with refoldIterateM.
989+
1008990-- | Like 'foldMany' but for the 'Refold' type. The supplied action is used as
1009991-- the initial value for each refold.
1010992--
@@ -1026,19 +1008,17 @@ refoldMany (Refold fstep inject extract) action (Stream step state) =
10261008 FL.Partial ps -> FoldManyLoop s ps
10271009
10281010 {-# INLINE_LATE step' #-}
1029− step' _ (FoldManyStart st) = do
1030− r <- action >>= inject
1031− return
1032− $ Skip
1033− $ case r of
1034− FL.Done b -> FoldManyYield b (FoldManyStart st)
1035− FL.Partial fs -> FoldManyFirst fs st
1036− step' gst (FoldManyFirst fs st) = do
1011+ step' gst (FoldManyStart st) = do
10371012 r <- step (adaptState gst) st
10381013 case r of
1039− Yield x s -> consume x s fs
1040− Skip s -> return $ Skip (FoldManyFirst fs s)
1014+ Yield x s -> return $ Skip (FoldManyFirst x s)
1015+ Skip s -> return $ Skip (FoldManyStart s)
10411016 Stop -> return Stop
1017+ step' _ (FoldManyFirst x st) = do
1018+ r <- action >>= inject
1019+ case r of
1020+ FL.Done b -> return $ Skip $ FoldManyYield b (FoldManyFirst x st)
1021+ FL.Partial fs -> consume x st fs
10421022 step' gst (FoldManyLoop st fs) = do
10431023 r <- step (adaptState gst) st
10441024 case r of