on master help sign in
Add some convenience funcs to construct folds
2b33253Harendra Kumar
src/Streamly/Internal/Data/Fold.hs+63−74
@@ -24,18 +24,17 @@
24module Streamly.Internal.Data.Fold
24module Streamly.Internal.Data.Fold
25 (
25 (
26 -- * Fold Type
26 -- * Fold Type
27− -- |
28− -- A 'Fold' can be run over a stream using the 'runFold' combinator in
29− -- "Streamly.Prelude":
30− --
31− -- >>> S.runFold FL.sum (S.enumerateFromTo 1 100)
32− -- 5050
33−
34 Fold (..)
27 Fold (..)
35
28
36 -- , tail
29 -- , tail
37 -- , init
30 -- , init
38
31
32+ -- * Fold Creation Utilities
33+ , mkPure
34+ , mkPureId
35+ , mkFold
36+ , mkFoldId
37+
39 -- ** Full Folds
38 -- ** Full Folds
40 , drain
39 , drain
41 , drainBy
40 , drainBy
@@ -62,9 +61,6 @@ module Streamly.Internal.Data.Fold
62 , foldMapM
61 , foldMapM
63
62
64 -- ** Full Folds (To Containers)
63 -- ** Full Folds (To Containers)
65− -- | Avoid using these folds in scalable or performance critical
66− -- applications, they buffer all the input in GC memory which can be
67− -- detrimental to performance if the input is large.
68
64
69 , toList
65 , toList
70 , toListRevF -- experimental
66 , toListRevF -- experimental
@@ -92,22 +88,6 @@ module Streamly.Internal.Data.Fold
92 , or
88 , or
93
89
94 -- * Transformations
90 -- * Transformations
95− -- | Unlike stream producer types (e.g. @SerialT m a@) which have only
96− -- output side, folds have an input side as well as an output side. In the
97− -- type @Fold m a b@, the input type is @a@ and the output type is @b@.
98− -- Transformations can be applied either on the input side or on the output
99− -- side. The 'Functor' instance of a fold maps on the output of the fold:
100− --
101− -- >>> S.runFold (fmap show FL.sum) (S.enumerateFromTo 1 100)
102− -- "5050"
103− --
104− -- However, the input side or contravariant transformations are more
105− -- interesting for folds. The following sections describe the input
106− -- transformation operations on a fold. The names of the operations are
107− -- consistent with their covariant counterparts in "Streamly.Prelude", the
108− -- only difference is that they are prefixed with 'l' which stands for
109− -- 'left' assuming left side is the input side, notice that in @Fold m a b@
110− -- the type variable @a@ is on the left side.
111
91
112 -- ** Covariant Operations
92 -- ** Covariant Operations
113 , sequence
93 , sequence
@@ -160,51 +140,17 @@ module Streamly.Internal.Data.Fold
160 , lchunksOf
140 , lchunksOf
161
141
162 -- * Distributing
142 -- * Distributing
163− -- |
164− -- The 'Applicative' instance of a distributing 'Fold' distributes one copy
165− -- of the stream to each fold and combines the results using a function.
166− --
167− -- @
168− --
169− -- |-------Fold m a b--------|
170− -- ---stream m a---| |---m (b,c,...)
171− -- |-------Fold m a c--------|
172− -- | |
173− -- ...
174− -- @
175− --
176− -- To compute the average of numbers in a stream without going throught he
177− -- stream twice:
178− --
179− -- >>> let avg = (/) <$> FL.sum <*> fmap fromIntegral FL.length
180− -- >>> S.runFold avg (S.enumerateFromTo 1.0 100.0)
181− -- 50.5
182− --
183− -- The 'Semigroup' and 'Monoid' instances of a distributing fold distribute
184− -- the input to both the folds and combines the outputs using Monoid or
185− -- Semigroup instances of the output types:
186− --
187− -- >>> import Data.Monoid (Sum)
188− -- >>> S.runFold (FL.head <> FL.last) (fmap Sum $ S.enumerateFromTo 1.0 100.0)
189− -- Just (Sum {getSum = 101.0})
190− --
191− -- The 'Num', 'Floating', and 'Fractional' instances work in the same way.
192
143
193 , tee
144 , tee
194 , distribute
145 , distribute
195
146
196 -- * Partitioning
147 -- * Partitioning
197− -- |
198− -- Direct items in the input stream to different folds using a binary
199− -- fold selector.
200
148
201 -- , partitionByM
149 -- , partitionByM
202 -- , partitionBy
150 -- , partitionBy
203 , partition
151 , partition
204
152
205 -- * Demultiplexing
153 -- * Demultiplexing
206− -- | Direct values in the input stream to different folds using an n-ary
207− -- fold selector.
208
154
209 , demux
155 , demux
210 -- , demuxWith
156 -- , demuxWith
@@ -212,8 +158,6 @@ module Streamly.Internal.Data.Fold
212 -- , demuxWith_
158 -- , demuxWith_
213
159
214 -- * Classifying
160 -- * Classifying
215− -- | In an input stream of key value pairs fold values for different keys
216− -- in individual output buckets using the given fold.
217
161
218 , classify
162 , classify
219 -- , classifyWith
163 -- , classifyWith
@@ -255,6 +199,51 @@ import Streamly.Internal.Data.Strict
255
199
256import qualified Streamly.Internal.Data.Pipe.Types as Pipe
200import qualified Streamly.Internal.Data.Pipe.Types as Pipe
257
201
202+------------------------------------------------------------------------------
203+-- Smart constructors
204+------------------------------------------------------------------------------
205+
206+-- | Make a fold using a pure step function, a pure initial state and
207+-- a pure state extraction function.
208+--
209+-- /Internal/
210+--
211+{-# INLINE mkPure #-}
212+mkPure :: Monad m => (s -> a -> s) -> s -> (s -> b) -> Fold m a b
213+mkPure step initial extract =
214+ Fold (\s a -> return $ step s a) (return initial) (return . extract)
215+
216+-- | Make a fold using a pure step function and a pure initial state. The
217+-- final state extracted is identical to the intermediate state.
218+--
219+-- /Internal/
220+--
221+{-# INLINE mkPureId #-}
222+mkPureId :: Monad m => (b -> a -> b) -> b -> Fold m a b
223+mkPureId step initial = mkPure step initial id
224+
225+-- | Make a fold with an effectful step function and initial state, and a state
226+-- extraction function.
227+--
228+-- > mkFold = Fold
229+--
230+-- We can just use 'Fold' but it is provided for completeness.
231+--
232+-- /Internal/
233+--
234+{-# INLINE mkFold #-}
235+mkFold :: (s -> a -> m s) -> m s -> (s -> m b) -> Fold m a b
236+mkFold = Fold
237+
238+-- | Make a fold with an effectful step function and initial state. The final
239+-- state extracted is identical to the intermediate state.
240+--
241+-- /Internal/
242+--
243+{-# INLINE mkFoldId #-}
244+mkFoldId :: Monad m => (b -> a -> m b) -> m b -> Fold m a b
245+mkFoldId step initial = Fold step initial return
246+
258------------------------------------------------------------------------------
247------------------------------------------------------------------------------
259-- Transformations on fold inputs
248-- Transformations on fold inputs
260------------------------------------------------------------------------------
249------------------------------------------------------------------------------
@@ -545,7 +534,7 @@ rollingHash = rollingHashWithSalt defaultSalt
545-- | Fold an input stream consisting of monoidal elements using 'mappend'
534-- | Fold an input stream consisting of monoidal elements using 'mappend'
546-- and 'mempty'.
535-- and 'mempty'.
547--
536--
548−-- > S.runFold FL.mconcat (S.map Sum $ S.enumerateFromTo 1 10)
537+-- > S.fold FL.mconcat (S.map Sum $ S.enumerateFromTo 1 10)
549--
538--
550-- @since 0.7.0
539-- @since 0.7.0
551{-# INLINABLE mconcat #-}
540{-# INLINABLE mconcat #-}
@@ -558,7 +547,7 @@ mconcat = Fold (\x a -> return $ mappend x a) (return mempty) return
558-- Make a fold from a pure function that folds the output of the function
547-- Make a fold from a pure function that folds the output of the function
559-- using 'mappend' and 'mempty'.
548-- using 'mappend' and 'mempty'.
560--
549--
561−-- > S.runFold (FL.foldMap Sum) $ S.enumerateFromTo 1 10
550+-- > S.fold (FL.foldMap Sum) $ S.enumerateFromTo 1 10
562--
551--
563-- @since 0.7.0
552-- @since 0.7.0
564{-# INLINABLE foldMap #-}
553{-# INLINABLE foldMap #-}
@@ -571,7 +560,7 @@ foldMap f = lmap f mconcat
571-- Make a fold from a monadic function that folds the output of the function
560-- Make a fold from a monadic function that folds the output of the function
572-- using 'mappend' and 'mempty'.
561-- using 'mappend' and 'mempty'.
573--
562--
574−-- > S.runFold (FL.foldMapM (return . Sum)) $ S.enumerateFromTo 1 10
563+-- > S.fold (FL.foldMapM (return . Sum)) $ S.enumerateFromTo 1 10
575--
564--
576-- @since 0.7.0
565-- @since 0.7.0
577{-# INLINABLE foldMapM #-}
566{-# INLINABLE foldMapM #-}
@@ -769,7 +758,7 @@ or = Fold (\x a -> return $ x || a) (return False) return
769-- ---stream m a---| |---m (b,c)
758-- ---stream m a---| |---m (b,c)
770-- |-------Fold m a c--------|
759-- |-------Fold m a c--------|
771-- @
760-- @
772−-- >>> S.runFold (FL.tee FL.sum FL.length) (S.enumerateFromTo 1.0 100.0)
761+-- >>> S.fold (FL.tee FL.sum FL.length) (S.enumerateFromTo 1.0 100.0)
773-- (5050.0,100)
762-- (5050.0,100)
774--
763--
775-- @since 0.7.0
764-- @since 0.7.0
@@ -811,7 +800,7 @@ foldCons (Fold stepL beginL doneL) (Fold stepR beginR doneR) =
811-- ...
800-- ...
812-- @
801-- @
813--
802--
814−-- >>> S.runFold (FL.distribute [FL.sum, FL.length]) (S.enumerateFromTo 1 5)
803+-- >>> S.fold (FL.distribute [FL.sum, FL.length]) (S.enumerateFromTo 1 5)
815-- [15,5]
804-- [15,5]
816--
805--
817-- This is the consumer side dual of the producer side 'sequence' operation.
806-- This is the consumer side dual of the producer side 'sequence' operation.
@@ -840,7 +829,7 @@ distribute (x:xs) = foldCons x (distribute xs)
840--
829--
841-- >>> import System.Random (randomIO)
830-- >>> import System.Random (randomIO)
842-- >>> randomly a = randomIO >>= \x -> return $ if x then Left a else Right a
831-- >>> randomly a = randomIO >>= \x -> return $ if x then Left a else Right a
843−-- >>> S.runFold (FL.partitionByM randomly FL.length FL.length) (S.enumerateFromTo 1 100)
832+-- >>> S.fold (FL.partitionByM randomly FL.length FL.length) (S.enumerateFromTo 1 100)
844-- (59,41)
833-- (59,41)
845--
834--
846-- Send input to the two folds in a proportion of 2:1:
835-- Send input to the two folds in a proportion of 2:1:
@@ -856,7 +845,7 @@ distribute (x:xs) = foldCons x (distribute xs)
856--
845--
857-- main = do
846-- main = do
858-- f <- proportionately 2 1
847-- f <- proportionately 2 1
859−-- r <- S.runFold (FL.partitionByM f FL.length FL.length) (S.enumerateFromTo (1 :: Int) 100)
848+-- r <- S.fold (FL.partitionByM f FL.length FL.length) (S.enumerateFromTo (1 :: Int) 100)
860-- print r
849-- print r
861-- @
850-- @
862-- @
851-- @
@@ -895,7 +884,7 @@ partitionByM f (Fold stepL beginL doneL) (Fold stepR beginR doneR) =
895-- >>> let f = FL.partitionBy (\\n -> if even n then Left n else Right n)
884-- >>> let f = FL.partitionBy (\\n -> if even n then Left n else Right n)
896-- (fmap (("Even " ++) . show) FL.length)
885-- (fmap (("Even " ++) . show) FL.length)
897-- (fmap (("Odd " ++) . show) FL.length)
886-- (fmap (("Odd " ++) . show) FL.length)
898−-- in S.runFold f (S.enumerateFromTo 1 100)
887+-- in S.fold f (S.enumerateFromTo 1 100)
899-- ("Even 50","Odd 50")
888-- ("Even 50","Odd 50")
900-- @
889-- @
901--
890--
@@ -982,7 +971,7 @@ demuxWith f kv = Fold step initial extract
982-- @
971-- @
983-- > let table = Data.Map.fromList [(\"SUM", FL.sum), (\"PRODUCT", FL.product)]
972-- > let table = Data.Map.fromList [(\"SUM", FL.sum), (\"PRODUCT", FL.product)]
984-- input = S.fromList [(\"SUM",1),(\"PRODUCT",2),(\"SUM",3),(\"PRODUCT",4)]
973-- input = S.fromList [(\"SUM",1),(\"PRODUCT",2),(\"SUM",3),(\"PRODUCT",4)]
985−-- in S.runFold (FL.demux table) input
974+-- in S.fold (FL.demux table) input
986-- One 1
975-- One 1
987-- Two 2
976-- Two 2
988-- @
977-- @
@@ -1041,7 +1030,7 @@ demuxWith_ f kv = Fold step initial extract
1041-- > let prn = FL.drainBy print
1030-- > let prn = FL.drainBy print
1042-- > let table = Data.Map.fromList [(\"ONE", prn), (\"TWO", prn)]
1031-- > let table = Data.Map.fromList [(\"ONE", prn), (\"TWO", prn)]
1043-- input = S.fromList [(\"ONE",1),(\"TWO",2)]
1032-- input = S.fromList [(\"ONE",1),(\"TWO",2)]
1044−-- in S.runFold (FL.demux_ table) input
1033+-- in S.fold (FL.demux_ table) input
1045-- One 1
1034-- One 1
1046-- Two 2
1035-- Two 2
1047-- @
1036-- @
@@ -1061,7 +1050,7 @@ demux_ = demuxWith_ id
1061--
1050--
1062-- @
1051-- @
1063-- > let input = S.fromList [(\"ONE",1),(\"ONE",1.1),(\"TWO",2), (\"TWO",2.2)]
1052-- > let input = S.fromList [(\"ONE",1),(\"ONE",1.1),(\"TWO",2), (\"TWO",2.2)]
1064−-- in S.runFold (FL.classify FL.toList) input
1053+-- in S.fold (FL.classify FL.toList) input
1065-- fromList [(\"ONE",[1.1,1.0]),(\"TWO",[2.2,2.0])]
1054-- fromList [(\"ONE",[1.1,1.0]),(\"TWO",[2.2,2.0])]
1066-- @
1055-- @
1067--
1056--
@@ -1091,7 +1080,7 @@ classifyWith f (Fold step initial extract) = Fold step' initial' extract'
1091--
1080--
1092-- @
1081-- @
1093-- > let input = S.fromList [(\"ONE",1),(\"ONE",1.1),(\"TWO",2), (\"TWO",2.2)]
1082-- > let input = S.fromList [(\"ONE",1),(\"ONE",1.1),(\"TWO",2), (\"TWO",2.2)]
1094−-- in S.runFold (FL.classify FL.toList) input
1083+-- in S.fold (FL.classify FL.toList) input
1095-- fromList [(\"ONE",[1.1,1.0]),(\"TWO",[2.2,2.0])]
1084-- fromList [(\"ONE",[1.1,1.0]),(\"TWO",[2.2,2.0])]
1096-- @
1085-- @
1097--
1086--
src/Streamly/Internal/Data/Fold/Types.hs+11−11
@@ -53,16 +53,16 @@ import Streamly.Internal.Data.SVar (MonadAsync)
53-- | Represents a left fold over an input stream of values of type @a@ to a
53-- | Represents a left fold over an input stream of values of type @a@ to a
54-- single value of type @b@ in 'Monad' @m@.
54-- single value of type @b@ in 'Monad' @m@.
55--
55--
56+-- The fold uses an intermediate state @s@ as accumulator. The @step@ function
57+-- updates the state and returns the new updated state. When the fold is done
58+-- the final result of the fold is extracted from the intermediate state
59+-- representation using the @extract@ function.
60+--
56-- @since 0.7.0
61-- @since 0.7.0
57
62
58−-- The fold uses an intermediate type @x@ as accumulator. The fold accumulator
59−-- is initialized by calling the @init@ function and is then driven by calling
60−-- the step function repeatedly. When the fold is done the @extract@ function
61−-- is used to map the intermediate type @x@ to the final type @b@. This allows
62−-- the state of the fold to be embedded in an arbitrary type @x@.
63data Fold m a b =
63data Fold m a b =
64 -- | @Fold @ @ step @ @ initial @ @ extract@
64 -- | @Fold @ @ step @ @ initial @ @ extract@
65− forall x. Fold (x -> a -> m x) (m x) (x -> m b)
65+ forall s. Fold (s -> a -> m s) (m s) (s -> m b)
66
66
67-- | Maps a function on the output of the fold (the type @b@).
67-- | Maps a function on the output of the fold (the type @b@).
68instance Applicative m => Functor (Fold m a) where
68instance Applicative m => Functor (Fold m a) where
@@ -219,7 +219,7 @@ toListRevF = Fold (\xs x -> return $ x:xs) (return []) return
219
219
220-- | @(lmap f fold)@ maps the function @f@ on the input of the fold.
220-- | @(lmap f fold)@ maps the function @f@ on the input of the fold.
221--
221--
222−-- >>> S.runFold (FL.lmap (\x -> x * x) FL.sum) (S.enumerateFromTo 1 100)
222+-- >>> S.fold (FL.lmap (\x -> x * x) FL.sum) (S.enumerateFromTo 1 100)
223-- 338350
223-- 338350
224--
224--
225-- @since 0.7.0
225-- @since 0.7.0
@@ -244,7 +244,7 @@ lmapM f (Fold step begin done) = Fold step' begin done
244
244
245-- | Include only those elements that pass a predicate.
245-- | Include only those elements that pass a predicate.
246--
246--
247−-- >>> S.runFold (lfilter (> 5) FL.sum) [1..10]
247+-- >>> S.fold (lfilter (> 5) FL.sum) [1..10]
248-- 40
248-- 40
249--
249--
250-- @since 0.7.0
250-- @since 0.7.0
@@ -313,9 +313,9 @@ ltakeWhile predicate (Fold step initial done) = Fold step' initial' done'
313-- accumulator. Thus we can resume the fold later and feed it more input.
313-- accumulator. Thus we can resume the fold later and feed it more input.
314--
314--
315-- >> do
315-- >> do
316−-- > more <- S.runFold (FL.duplicate FL.sum) (S.enumerateFromTo 1 10)
316+-- > more <- S.fold (FL.duplicate FL.sum) (S.enumerateFromTo 1 10)
317−-- > evenMore <- S.runFold (FL.duplicate more) (S.enumerateFromTo 11 20)
317+-- > evenMore <- S.fold (FL.duplicate more) (S.enumerateFromTo 11 20)
318−-- > S.runFold evenMore (S.enumerateFromTo 21 30)
318+-- > S.fold evenMore (S.enumerateFromTo 21 30)
319-- > 465
319-- > 465
320--
320--
321-- @since 0.7.0
321-- @since 0.7.0