on master help sign in
Add some convenience funcs to construct folds
commit2b332538f4698042182658ea61491322a150ee2f authorHarendra Kumar <harendra.kumar@gmail.com> dateSat 9 Nov 2019, 19:07 (7 years ago) parentfebb5a1
2 files changed+74−85
src/Streamly/Internal/Data/Fold.hs+63−74 src/Streamly/Internal/Data/Fold/Types.hs+11−11
src/Streamly/Internal/Data/Fold.hs+63−74
@@ -24,18 +24,17 @@
2424module Streamly.Internal.Data.Fold
2525 (
2626 -- * 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−
3427 Fold (..)
3528
3629 -- , tail
3730 -- , init
3831
32+ -- * Fold Creation Utilities
33+ , mkPure
34+ , mkPureId
35+ , mkFold
36+ , mkFoldId
37+
3938 -- ** Full Folds
4039 , drain
4140 , drainBy
@@ -62,9 +61,6 @@ module Streamly.Internal.Data.Fold
6261 , foldMapM
6362
6463 -- ** 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.
6864
6965 , toList
7066 , toListRevF -- experimental
@@ -92,22 +88,6 @@ module Streamly.Internal.Data.Fold
9288 , or
9389
9490 -- * 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.
11191
11292 -- ** Covariant Operations
11393 , sequence
@@ -160,51 +140,17 @@ module Streamly.Internal.Data.Fold
160140 , lchunksOf
161141
162142 -- * 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.
192143
193144 , tee
194145 , distribute
195146
196147 -- * Partitioning
197− -- |
198− -- Direct items in the input stream to different folds using a binary
199− -- fold selector.
200148
201149 -- , partitionByM
202150 -- , partitionBy
203151 , partition
204152
205153 -- * Demultiplexing
206− -- | Direct values in the input stream to different folds using an n-ary
207− -- fold selector.
208154
209155 , demux
210156 -- , demuxWith
@@ -212,8 +158,6 @@ module Streamly.Internal.Data.Fold
212158 -- , demuxWith_
213159
214160 -- * Classifying
215− -- | In an input stream of key value pairs fold values for different keys
216− -- in individual output buckets using the given fold.
217161
218162 , classify
219163 -- , classifyWith
@@ -255,6 +199,51 @@ import Streamly.Internal.Data.Strict
255199
256200import qualified Streamly.Internal.Data.Pipe.Types as Pipe
257201
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+
258247------------------------------------------------------------------------------
259248-- Transformations on fold inputs
260249------------------------------------------------------------------------------
@@ -545,7 +534,7 @@ rollingHash = rollingHashWithSalt defaultSalt
545534-- | Fold an input stream consisting of monoidal elements using 'mappend'
546535-- and 'mempty'.
547536--
548−-- > S.runFold FL.mconcat (S.map Sum $ S.enumerateFromTo 1 10)
537+-- > S.fold FL.mconcat (S.map Sum $ S.enumerateFromTo 1 10)
549538--
550539-- @since 0.7.0
551540{-# INLINABLE mconcat #-}
@@ -558,7 +547,7 @@ mconcat = Fold (\x a -> return $ mappend x a) (return mempty) return
558547-- Make a fold from a pure function that folds the output of the function
559548-- using 'mappend' and 'mempty'.
560549--
561−-- > S.runFold (FL.foldMap Sum) $ S.enumerateFromTo 1 10
550+-- > S.fold (FL.foldMap Sum) $ S.enumerateFromTo 1 10
562551--
563552-- @since 0.7.0
564553{-# INLINABLE foldMap #-}
@@ -571,7 +560,7 @@ foldMap f = lmap f mconcat
571560-- Make a fold from a monadic function that folds the output of the function
572561-- using 'mappend' and 'mempty'.
573562--
574−-- > S.runFold (FL.foldMapM (return . Sum)) $ S.enumerateFromTo 1 10
563+-- > S.fold (FL.foldMapM (return . Sum)) $ S.enumerateFromTo 1 10
575564--
576565-- @since 0.7.0
577566{-# INLINABLE foldMapM #-}
@@ -769,7 +758,7 @@ or = Fold (\x a -> return $ x || a) (return False) return
769758-- ---stream m a---| |---m (b,c)
770759-- |-------Fold m a c--------|
771760-- @
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)
773762-- (5050.0,100)
774763--
775764-- @since 0.7.0
@@ -811,7 +800,7 @@ foldCons (Fold stepL beginL doneL) (Fold stepR beginR doneR) =
811800-- ...
812801-- @
813802--
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)
815804-- [15,5]
816805--
817806-- This is the consumer side dual of the producer side 'sequence' operation.
@@ -840,7 +829,7 @@ distribute (x:xs) = foldCons x (distribute xs)
840829--
841830-- >>> import System.Random (randomIO)
842831-- >>> 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)
844833-- (59,41)
845834--
846835-- Send input to the two folds in a proportion of 2:1:
@@ -856,7 +845,7 @@ distribute (x:xs) = foldCons x (distribute xs)
856845--
857846-- main = do
858847-- 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)
860849-- print r
861850-- @
862851-- @
@@ -895,7 +884,7 @@ partitionByM f (Fold stepL beginL doneL) (Fold stepR beginR doneR) =
895884-- >>> let f = FL.partitionBy (\\n -> if even n then Left n else Right n)
896885-- (fmap (("Even " ++) . show) FL.length)
897886-- (fmap (("Odd " ++) . show) FL.length)
898−-- in S.runFold f (S.enumerateFromTo 1 100)
887+-- in S.fold f (S.enumerateFromTo 1 100)
899888-- ("Even 50","Odd 50")
900889-- @
901890--
@@ -982,7 +971,7 @@ demuxWith f kv = Fold step initial extract
982971-- @
983972-- > let table = Data.Map.fromList [(\"SUM", FL.sum), (\"PRODUCT", FL.product)]
984973-- 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
986975-- One 1
987976-- Two 2
988977-- @
@@ -1041,7 +1030,7 @@ demuxWith_ f kv = Fold step initial extract
10411030-- > let prn = FL.drainBy print
10421031-- > let table = Data.Map.fromList [(\"ONE", prn), (\"TWO", prn)]
10431032-- input = S.fromList [(\"ONE",1),(\"TWO",2)]
1044−-- in S.runFold (FL.demux_ table) input
1033+-- in S.fold (FL.demux_ table) input
10451034-- One 1
10461035-- Two 2
10471036-- @
@@ -1061,7 +1050,7 @@ demux_ = demuxWith_ id
10611050--
10621051-- @
10631052-- > 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
10651054-- fromList [(\"ONE",[1.1,1.0]),(\"TWO",[2.2,2.0])]
10661055-- @
10671056--
@@ -1091,7 +1080,7 @@ classifyWith f (Fold step initial extract) = Fold step' initial' extract'
10911080--
10921081-- @
10931082-- > 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
10951084-- fromList [(\"ONE",[1.1,1.0]),(\"TWO",[2.2,2.0])]
10961085-- @
10971086--
src/Streamly/Internal/Data/Fold/Types.hs+11−11
@@ -53,16 +53,16 @@ import Streamly.Internal.Data.SVar (MonadAsync)
5353-- | Represents a left fold over an input stream of values of type @a@ to a
5454-- single value of type @b@ in 'Monad' @m@.
5555--
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+--
5661-- @since 0.7.0
5762
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@.
6363data Fold m a b =
6464 -- | @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)
6666
6767-- | Maps a function on the output of the fold (the type @b@).
6868instance Applicative m => Functor (Fold m a) where
@@ -219,7 +219,7 @@ toListRevF = Fold (\xs x -> return $ x:xs) (return []) return
219219
220220-- | @(lmap f fold)@ maps the function @f@ on the input of the fold.
221221--
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)
223223-- 338350
224224--
225225-- @since 0.7.0
@@ -244,7 +244,7 @@ lmapM f (Fold step begin done) = Fold step' begin done
244244
245245-- | Include only those elements that pass a predicate.
246246--
247−-- >>> S.runFold (lfilter (> 5) FL.sum) [1..10]
247+-- >>> S.fold (lfilter (> 5) FL.sum) [1..10]
248248-- 40
249249--
250250-- @since 0.7.0
@@ -313,9 +313,9 @@ ltakeWhile predicate (Fold step initial done) = Fold step' initial' done'
313313-- accumulator. Thus we can resume the fold later and feed it more input.
314314--
315315-- >> do
316−-- > more <- S.runFold (FL.duplicate FL.sum) (S.enumerateFromTo 1 10)
317−-- > evenMore <- S.runFold (FL.duplicate more) (S.enumerateFromTo 11 20)
318−-- > S.runFold evenMore (S.enumerateFromTo 21 30)
316+-- > more <- S.fold (FL.duplicate FL.sum) (S.enumerateFromTo 1 10)
317+-- > evenMore <- S.fold (FL.duplicate more) (S.enumerateFromTo 11 20)
318+-- > S.fold evenMore (S.enumerateFromTo 21 30)
319319-- > 465
320320--
321321-- @since 0.7.0