on master help sign in
Update Fold docs, move containers ops to streamly pkg

Move the container dependent code from streamly-core to streamly Rename some functions. Release some functions.

commit703823b3d5764369f6c7aa0338139c2e91daccaf authorHarendra Kumar <harendra@composewell.com> dateWed 2 Nov 2022, 05:27 (4 years ago) parent443a2c6
36 files changed+1740−1221
benchmark/Streamly/Benchmark/Data/Fold.hs+1 benchmark/Streamly/Benchmark/Data/Stream/Expand.hs+1 core/src/Streamly/Data/Fold.hs+59−25 core/src/Streamly/Data/Fold/Tee.hs+1−1 core/src/Streamly/Data/Parser.hs−1 core/src/Streamly/Data/Stream.hs+4−1 core/src/Streamly/Internal/Data/Fold.hs+378−726 core/src/Streamly/Internal/Data/Fold/Type.hs+223−94 core/src/Streamly/Internal/Data/Fold/Window.hs−37 core/src/Streamly/Internal/Data/Parser.hs+52−11 core/src/Streamly/Internal/Data/Stream/Bottom.hs+52−40 core/src/Streamly/Internal/Data/Stream/Eliminate.hs+17−5 core/src/Streamly/Internal/Data/Stream/Exception.hs−45 core/src/Streamly/Internal/Data/Stream/Generate.hs+4−4 core/src/Streamly/Internal/Data/Stream/StreamD/Exception.hs−55 core/src/Streamly/Internal/Data/Stream/StreamD/Transform.hs−22 core/src/Streamly/Internal/Data/Stream/StreamD/Type.hs−3 core/src/Streamly/Internal/Data/Stream/Top.hs−115 core/src/Streamly/Internal/Data/Stream/Transform.hs+4−4 core/src/Streamly/Internal/Data/Stream/Type.hs+19−3 core/src/Streamly/Internal/FileSystem/File.hs+2−2 core/src/Streamly/Internal/FileSystem/Handle.hs+2−2 core/streamly-core.cabal+12−3 src/Streamly/Internal/Data/Fold/Extra.hs+623 core/src/Streamly/Internal/Data/IsMap.hs → src/Streamly/Internal/Data/IsMap.hs src/Streamly/Internal/Data/Stream/Exception/Lifted.hs+104−7 src/Streamly/Internal/Data/Stream/Extra.hs+164 src/Streamly/Internal/Data/Stream/IsStream/Common.hs+1−1 src/Streamly/Internal/Data/Stream/IsStream/Exception.hs+2−2 src/Streamly/Internal/Data/Stream/Time.hs+2−2 src/Streamly/Internal/FileSystem/Event/Linux.hs+1−1 src/Streamly/Internal/FileSystem/Event/Windows.hs+1−1 src/Streamly/Internal/Network/Inet/TCP.hs+4−5 src/Streamly/Internal/Network/Socket.hs+2−2 streamly.cabal+4−1 test/Streamly/Test/Data/Fold.hs+1
benchmark/Streamly/Benchmark/Data/Fold.hs+1
@@ -28,6 +28,7 @@ import Streamly.Internal.Data.Fold (Fold(..))
2828import Streamly.Internal.Data.IsMap.HashMap ()
2929
3030import qualified Streamly.Internal.Data.Fold as FL
31+import qualified Streamly.Internal.Data.Fold.Extra as FL
3132import qualified Streamly.Internal.Data.Unfold as Unfold
3233import qualified Streamly.Internal.Data.Pipe as Pipe
3334import qualified Streamly.Internal.Data.Stream as Stream
benchmark/Streamly/Benchmark/Data/Stream/Expand.hs+1
@@ -45,6 +45,7 @@ import Streamly.Benchmark.Prelude
4545 , sourceFoldMapWithStream, concatFoldableWith, concatForFoldableWith)
4646#else
4747import qualified Streamly.Internal.Data.Stream as S
48+import qualified Streamly.Internal.Data.Stream.Extra as S
4849#endif
4950import qualified Streamly.Internal.Data.Unfold as UF
5051import qualified Streamly.Internal.Data.Fold as Fold
core/src/Streamly/Data/Fold.hs+59−25
@@ -15,8 +15,8 @@
1515-- >>> import qualified Streamly.Data.Stream as Stream
1616--
1717-- For example, a 'sum' Fold represents adding the input to the accumulated
18−-- sum. A fold driver e.g. 'Streamly.Prelude.fold' pushes values from a stream
19−-- to the 'Fold' one at a time, reducing the stream to a single value.
18+-- sum. A fold driver pushes values from a stream to the 'Fold' one at a time,
19+-- reducing the stream to a single value.
2020--
2121-- >>> Stream.fold Fold.sum $ Stream.fromList [1..100]
2222-- 5050
@@ -93,7 +93,7 @@
9393--
9494-- We can often use streams or folds to achieve the same goal. However, streams
9595-- are more efficient in composition of producers (e.g.
96−-- 'Streamly.Prelude.serial' or 'Streamly.Prelude.mergeBy') whereas folds are
96+-- 'Data.Stream.append' or 'Data.Stream.mergeBy') whereas folds are
9797-- more efficient in composition of consumers (e.g. 'serialWith', 'partition'
9898-- or 'teeWith').
9999--
@@ -135,7 +135,8 @@ module Streamly.Data.Fold
135135 -- * Constructors
136136 , foldl'
137137 , foldlM'
138− , foldr
138+ , foldl1'
139+ , foldr'
139140
140141 -- * Folds
141142 -- ** Accumulators
@@ -152,17 +153,10 @@ module Streamly.Data.Fold
152153
153154 -- Reducers
154155 , drain
155− , drainBy
156− , the
157− , uniq
158− , last
156+ , drainMapM
159157 , length
160158 , sum
161159 , product
162− , maximumBy
163− , maximum
164− , minimumBy
165− , minimum
166160 , mean
167161 , rollingHash
168162 , rollingHashWithSalt
@@ -171,16 +165,38 @@ module Streamly.Data.Fold
171165 , toList
172166 , toListRev
173167
168+ -- ** Non-Empty Accumulators
169+ -- | Accumulators that do not have a default value, therefore, return
170+ -- 'Nothing' on an empty stream.
171+ , latest
172+ , maximumBy
173+ , maximum
174+ , minimumBy
175+ , minimum
176+
177+ -- ** Filtering Scanners
178+ -- | Accumulators that are usually run as a scan using the 'scanMaybe'
179+ -- combinator.
180+ , uniq
181+ , uniqBy
182+ , deleteBy
183+ , findIndices
184+ , elemIndices
185+
174186 -- ** Terminating Folds
175187 -- | These are much like lazy right folds.
176188
177− , index
178189 , one
190+ , null
191+ -- , satisfy
192+ -- , maybe
193+
194+ , index
195+ , the
179196 , find
180197 , lookup
181198 , findIndex
182199 , elemIndex
183− , null
184200 , elem
185201 , notElem
186202 , all
@@ -188,24 +204,36 @@ module Streamly.Data.Fold
188204 , and
189205 , or
190206
207+ -- * Running A Fold
208+ , drive
209+ , driveBreak
210+
211+ -- * Building Incrmentally
212+ , extractM
213+ , reduce
214+ , snoc
215+ -- , snocl
216+ , snocM
217+ -- , snoclM
218+ , augment
219+ , duplicate
220+ -- , isClosed
221+
191222 -- * Combinators
192223 -- | Combinators are modifiers of folds. In the type @Fold m a b@, @a@ is
193224 -- the input type and @b@ is the output type. Transformations can be
194− -- applied either on the input side or on the output side. Therefore,
195− -- combinators are of one of the following general shapes:
225+ -- applied either on the input side (contravariant) or on the output side
226+ -- (covariant). Therefore, combinators are of one of the following general
227+ -- shapes:
196228 --
197229 -- * @... -> Fold m a b -> Fold m c b@ (input transformation)
198230 -- * @... -> Fold m a b -> Fold m a c@ (output transformation)
199231 --
200− -- Output transformations are also known as covariant transformations, and
201− -- input transformations are also known as contravariant transformations.
202232 -- The input side transformations are more interesting for folds. Most of
203233 -- the following sections describe the input transformation operations on a
204− -- fold. The names and signatures of the operations are consistent with
205− -- corresponding operations in "Streamly.Prelude". When an operation makes
206− -- sense on both input and output side we use the prefix @l@ (for left) for
207− -- input side operations and the prefix @r@ (for right) for output side
208− -- operations.
234+ -- fold. When an operation makes sense on both input and output side we use
235+ -- the prefix @l@ (for left) for input side operations and the prefix @r@
236+ -- (for right) for output side operations.
209237
210238 -- ** Mapping on output
211239 -- | The 'Functor' instance of a fold maps on the output of the fold:
@@ -219,7 +247,10 @@ module Streamly.Data.Fold
219247 , lmap
220248 , lmapM
221249
222− -- ** Filtering
250+ -- ** Scanning and Filtering
251+ , scan
252+ , postscan
253+ , scanMaybe
223254 , filter
224255 , filterM
225256
@@ -268,6 +299,9 @@ module Streamly.Data.Fold
268299 , concatMap
269300
270301 -- * Deprecated
302+ , foldr
303+ , drainBy
304+ , last
271305 , head
272306 , sequence
273307 , mapM
@@ -282,7 +316,7 @@ import Prelude
282316 notElem, maximum, minimum, head, last, tail, length, null,
283317 reverse, iterate, init, and, or, lookup, foldr1, (!!),
284318 scanl, scanl1, replicate, concatMap, mconcat, foldMap, unzip,
285− span, splitAt, break, mapM)
319+ span, splitAt, break, mapM, maybe)
286320
287321import Streamly.Internal.Data.Fold
288322
core/src/Streamly/Data/Fold/Tee.hs+1−1
@@ -27,7 +27,7 @@
2727-- instances of the output types:
2828--
2929-- >>> import Data.Monoid (Sum(..))
30−-- >>> t = Tee Fold.one <> Tee Fold.last
30+-- >>> t = Tee Fold.one <> Tee Fold.latest
3131-- >>> Stream.fold (toFold t) (fmap Sum $ Stream.enumerateFromTo 1.0 100.0)
3232-- Just (Sum {getSum = 101.0})
3333--
core/src/Streamly/Data/Parser.hs−1
@@ -18,7 +18,6 @@ module Streamly.Data.Parser
1818 -- * Parser Type
1919 Parser
2020
21− -- XXX Should we use Fold.fromParser instead?
2221 -- -- * Downgrade to Fold
2322 -- , toFold
2423
core/src/Streamly/Data/Stream.hs+4−1
@@ -242,7 +242,10 @@ module Streamly.Data.Stream
242242 , fold -- XXX rename to run? We can have a Stream.run and Fold.run.
243243 -- XXX fold1 can be achieved using Monoids or Refolds.
244244 , foldBreak
245− , foldContinue
245+
246+ -- * Builders
247+ , build
248+ -- , buildl
246249
247250 -- ** Parsing
248251 , parse
core/src/Streamly/Internal/Data/Fold.hs+378−726
@@ -8,12 +8,13 @@
88-- Portability : GHC
99--
1010-- See "Streamly.Data.Fold" for an overview and
11−-- "Streamly.Internal.Data.Fold.Types" for design notes.
12−--
13−-- IMPORTANT: keep the signatures consistent with the folds in Streamly.Prelude
11+-- "Streamly.Internal.Data.Fold.Type" for design notes.
1412
1513module Streamly.Internal.Data.Fold
1614 (
15+ -- * Imports
16+ -- $setup
17+
1718 -- * Fold Type
1819 Step (..)
1920 , Fold (..)
@@ -22,19 +23,17 @@ module Streamly.Internal.Data.Fold
2223 -- | Which constructor to use?
2324 --
2425 -- * @foldl*@: If the fold never terminates i.e. does not use the 'Done'
25− -- constructor otherwise use the @mkFold@ variants.
26+ -- constructor otherwise use the @foldt*@ variants.
2627 -- * @*M@: Use the @M@ suffix variants if any of the step, initial, or
2728 -- extract function is monadic, otherwise use the pure variants.
28− -- * @*_@: Use the @_@ suffix variants if the extract function is 'id' or
29− -- 'return'.
3029 --
3130 , foldl'
3231 , foldlM'
3332 , foldl1'
3433 , foldt'
3534 , foldtM'
36− , foldr
37− , foldrM
35+ , foldr'
36+ , foldrM'
3837
3938 -- * Mappers
4039 -- | Monadic functions useful with mapM/lmapM on folds or streams.
@@ -42,9 +41,6 @@ module Streamly.Internal.Data.Fold
4241 , trace
4342
4443 -- * Folds
45− -- ** Identity
46− , fromPure
47− , fromEffect
4844
4945 -- ** Accumulators
5046 -- *** Semigroups and Monoids
@@ -55,15 +51,13 @@ module Streamly.Internal.Data.Fold
5551
5652 -- *** Reducers
5753 , drain
58− , drainBy
54+ , drainMapM
5955 , the
6056 , length
61− , genericLength
62− , countDistinct
63− , countDistinctInt
64− , frequency
57+ , lengthGeneric
6558 , mean
6659 , rollingHash
60+ , defaultSalt
6761 , rollingHashWithSalt
6862 , rollingHashFirstN
6963 -- , rollingHashLastN
@@ -88,8 +82,7 @@ module Streamly.Internal.Data.Fold
8882 -- $toListRev
8983 , toStream
9084 , toStreamRev
91− , toMap
92− , topBy
85+ , bottomBy
9386 , top
9487 , bottom
9588
@@ -98,7 +91,7 @@ module Streamly.Internal.Data.Fold
9891 -- the 'scanMaybe' combinator. For scanners the result of the fold is
9992 -- usually a transformation of the current element rather than an
10093 -- aggregation of all elements till now.
101− , last -- XXX prev
94+ , latest
10295 -- , nthLast -- using Ring array
10396 , indexingWith
10497 , indexing
@@ -109,8 +102,6 @@ module Streamly.Internal.Data.Fold
109102 -- | Useful in combination with the 'scanMaybe' combinator.
110103 , filtering
111104 , deleteBy
112− , nub
113− , nubInt
114105 , uniqBy
115106 , uniq
116107 , repeated
@@ -118,18 +109,25 @@ module Streamly.Internal.Data.Fold
118109 , elemIndices
119110
120111 -- ** Terminating Folds
121− -- Element folds. Terminate after inspecting one element. All these can be
122− -- implemented in terms of the 'maybe' fold.
112+ -- *** Empty folds
113+ -- | Folds that return a result without consuming any input.
114+ , fromPure
115+ , fromEffect
116+
117+ -- *** Singleton folds
118+ -- | Folds that terminate after consuming exactly one input element. All
119+ -- these can be implemented in terms of the 'maybe' fold.
123120 , one
124121 , null
125122 , satisfy
126123 , maybe
127124
128− -- Sequence folds. Terminate after inspecting a sequence of elements.
125+ -- *** Multi folds
126+ -- | Terminate after consuming one or more elements.
129127 , drainN
130128 -- , lastN
131129 -- , (!!)
132− , genericIndex
130+ , indexGeneric
133131 , index
134132 , findM
135133 , find
@@ -155,6 +153,20 @@ module Streamly.Internal.Data.Fold
155153 , droppingWhile
156154 , prune
157155
156+ -- * Running A Fold
157+ , drive
158+ , driveBreak
159+
160+ -- * Building Incrementally
161+ , extractM
162+ , reduce
163+ , isClosed
164+ , snoc
165+ , snocl
166+ , snocM
167+ , snoclM
168+ , augment
169+
158170 -- * Combinators
159171 -- ** Utilities
160172 , with
@@ -182,8 +194,8 @@ module Streamly.Internal.Data.Fold
182194 , indexed
183195
184196 -- ** Zipping Input
185− , zipWithM
186− , zip
197+ , zipStreamWithM
198+ , zipStream
187199
188200 -- ** Filtering Input
189201 , catMaybes
@@ -260,41 +272,6 @@ module Streamly.Internal.Data.Fold
260272 , partitionBy
261273 , partition
262274
263− -- ** Demultiplexing
264− -- | Direct values in the input stream to different folds using an n-ary
265− -- fold selector. 'demux' is a generalization of 'classify' (and
266− -- 'partition') where each key of the classifier can use a different fold.
267− , demux
268− , demuxWith
269− , demuxScanWith
270− , demuxScanMutWith
271− , demuxMutWith
272−
273− -- TODO: These can be implemented using the above operations
274− -- , demuxWithSel -- Stop when the fold for the specified key stops
275− -- , demuxWithMin -- Stop when any of the folds stop
276− -- , demuxWithAll -- Stop when all the folds stop (run once)
277−
278− -- ** Classifying
279− -- | In an input stream of key value pairs fold values for different keys
280− -- in individual output buckets using the given fold. 'classify' is a
281− -- special case of 'demux' where all the branches of the demultiplexer use
282− -- the same fold.
283− --
284− -- Different types of maps can be used with these combinators via the IsMap
285− -- type class. Hashmap performs better when there are more collisions, trie
286− -- Map performs better otherwise. Trie has an advantage of sorting the keys
287− -- at the same time. For example if we want to store a dictionary of words
288− -- and their meanings then trie Map would be better if we also want to
289− -- display them in sorted order.
290−
291− , classify
292− , classifyWith
293− , classifyMutWith
294− , classifyScanWith
295− -- , classifyWithSel
296− -- , classifyWithMin
297−
298275 -- ** Splitting
299276 , many
300277 , chunksOf
@@ -304,20 +281,12 @@ module Streamly.Internal.Data.Fold
304281 , unfoldMany
305282 , concatSequence
306283 , concatMap
307−
308− -- * Running A Fold
309− -- | Normally you would run a fold to completion by supplying it a stream,
310− -- e.g. using 'Stream.fold'. However, you could also run a fold partially
311− -- by using 'duplicate' on it and then running it with a stream.
312− -- Alternatively, 'initialize', 'snoc' and 'finish' can be used to run a
313− -- fold incrementally, however, that may not be the most efficient way to
314− -- run a fold.
315− , initialize
316− , snoc
317284 , duplicate
318− , finish
319285
320286 -- * Deprecated
287+ , foldr
288+ , drainBy
289+ , last
321290 , head
322291 , sequence
323292 , mapM
@@ -336,11 +305,8 @@ import Data.Bits (shiftL, shiftR, (.|.), (.&.))
336305import Data.Either (isLeft, isRight, fromLeft, fromRight)
337306import Data.Functor.Identity (Identity(..))
338307import Data.Int (Int64)
339−import Data.IORef (newIORef, readIORef, writeIORef)
340−import Data.Map.Strict (Map)
341308import Data.Word (Word32)
342309import Foreign.Storable (peek, sizeOf)
343−import Streamly.Internal.Data.IsMap (IsMap(..))
344310import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)
345311import Streamly.Internal.Data.Pipe.Type (Pipe (..), PipeState(..))
346312import Streamly.Internal.Data.Unboxed (Unbox)
@@ -348,16 +314,14 @@ import Streamly.Internal.Data.Unfold.Type (Unfold(..))
348314import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))
349315import Streamly.Internal.Data.Stream.Type (Stream)
350316
351−import qualified Data.IntSet as IntSet
352−import qualified Data.Set as Set
353317import qualified Prelude
354318import qualified Streamly.Internal.Data.Array.Unboxed.Mut.Type as MA
355319import qualified Streamly.Internal.Data.Array.Unboxed.Type as Array
356320import qualified Streamly.Internal.Data.Fold.Window as FoldW
357−import qualified Streamly.Internal.Data.IsMap as IsMap
358321import qualified Streamly.Internal.Data.Pipe.Type as Pipe
359322import qualified Streamly.Internal.Data.Ring.Unboxed as Ring
360323import qualified Streamly.Internal.Data.Stream.StreamD.Type as StreamD
324+import qualified Streamly.Internal.Data.Stream.StreamK as K
361325import qualified Streamly.Internal.Data.Stream.Type as Stream
362326
363327import Prelude hiding
@@ -371,7 +335,13 @@ import Streamly.Internal.Data.Fold.Type
371335
372336-- $setup
373337-- >>> :m
374−-- >>> import Prelude hiding (break, map, span, splitAt)
338+-- >>> :set -package random
339+-- >>> import Control.Monad (void)
340+-- >>> import Data.Functor.Identity (runIdentity)
341+-- >>> import Data.Maybe (isJust)
342+-- >>> import Data.Monoid (Last(..), Sum(..))
343+-- >>> import Streamly.Data.Stream (Stream)
344+-- >>> import Data.IORef (newIORef, readIORef, writeIORef)
375345-- >>> import qualified Streamly.Data.Array.Unboxed as Array
376346-- >>> import qualified Streamly.Data.Fold as Fold
377347-- >>> import qualified Streamly.Data.Stream as Stream
@@ -382,8 +352,60 @@ import Streamly.Internal.Data.Fold.Type
382352-- >>> import qualified Streamly.Internal.Data.Parser as Parser
383353-- >>> import qualified Streamly.Internal.Data.Stream.Type as Stream
384354-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
385−-- >>> import Streamly.Internal.Data.Stream.Type (Stream)
386−-- >>> import Data.IORef (newIORef, readIORef, writeIORef)
355+-- >>> import Prelude hiding (break, map, span, splitAt)
356+
357+------------------------------------------------------------------------------
358+-- Running
359+------------------------------------------------------------------------------
360+
361+-- | Drive a fold using the supplied 'Stream', reducing the resulting
362+-- expression strictly at each step.
363+--
364+-- Definition:
365+--
366+-- >>> drive = flip Stream.fold
367+--
368+-- Example:
369+--
370+-- >>> Fold.drive (Stream.enumerateFromTo 1 100) Fold.sum
371+-- 5050
372+--
373+{-# INLINE drive #-}
374+drive :: Monad m => Stream m a -> Fold m a b -> m b
375+drive strm fl = StreamD.fold fl $ StreamD.fromStreamK $ Stream.toStreamK strm
376+
377+-- | Like 'drive' but also returns the remaining stream. The resulting stream
378+-- would be 'Stream.nil' if the stream finished before the fold.
379+--
380+-- Definition:
381+--
382+-- >>> driveBreak = flip Stream.foldBreak
383+--
384+-- /Not fused/
385+--
386+{-# INLINE driveBreak #-}
387+driveBreak :: Monad m => Stream m a -> Fold m a b -> m (b, Stream m a)
388+driveBreak strm fl = fmap f $ K.foldBreak fl (Stream.toStreamK strm)
389+
390+ where
391+
392+ f (b, str) = (b, Stream.fromStreamK str)
393+
394+-- | Augment a fold strictly by adding a stream to it.
395+--
396+-- Definitions:
397+--
398+-- >>> augment stream = Fold.drive stream . Fold.duplicate
399+--
400+-- >>> :{
401+-- Fold.augment (Stream.enumerateFromTo 1 10) Fold.sum
402+-- >>= Fold.augment (Stream.enumerateFromTo 11 20)
403+-- >>= Fold.drive (Stream.enumerateFromTo 21 30)
404+-- :}
405+-- 465
406+--
407+augment :: Monad m => Stream m a -> Fold m a b -> m (Fold m a b)
408+augment stream = drive stream . duplicate
387409
388410------------------------------------------------------------------------------
389411-- hoist
@@ -398,7 +420,7 @@ hoist f (Fold step initial extract) =
398420
399421-- | Adapt a pure fold to any monad
400422--
401−-- > generally = Fold.hoist (return . runIdentity)
423+-- >>> generally = Fold.hoist (return . runIdentity)
402424--
403425-- /Pre-release/
404426generally :: Monad m => Fold Identity a b -> Fold m a b
@@ -575,10 +597,12 @@ scanMany = scanWith True
575597-- Filters
576598------------------------------------------------------------------------------
577599
578−-- | A filtering scan that deletes the first occurrence of the element in the
579−-- stream that satisfies the given equality predicate.
600+-- | Returns the latest element omitting the first occurrence that satisfies
601+-- the given equality predicate.
602+--
603+-- Example:
580604--
581−-- >>> input = Stream.unfold Unfold.fromList [1,3,3,5]
605+-- >>> input = Stream.fromList [1,3,3,5]
582606-- >>> Stream.fold Fold.toList $ Stream.scanMaybe (Fold.deleteBy (==) 3) input
583607-- [1,3,5]
584608--
@@ -614,20 +638,19 @@ slide2 (Fold step1 initial1 extract1) = Fold step initial extract
614638
615639 extract (Tuple' _ s) = extract1 s
616640
617−-- | Drop repeated elements that are adjacent to each other using the supplied
618−-- comparison function.
641+-- | Return the latest unique element using the supplied comparison function.
642+-- Returns 'Nothing' if the current element is same as the last element
643+-- otherwise returns 'Just'.
619644--
620−-- To strip duplicate path separators:
645+-- Example, strip duplicate path separators:
621646--
622−-- >>> input = Stream.unfold Unfold.fromList "//a//b"
647+-- >>> input = Stream.fromList "//a//b"
623648-- >>> f x y = x == '/' && y == '/'
624649-- >>> Stream.fold Fold.toList $ Stream.scanMaybe (Fold.uniqBy f) input
625650-- "/a/b"
626651--
627652-- Space: @O(1)@
628653--
629−-- See also: 'nubBy'.
630−--
631654-- /Pre-release/
632655--
633656{-# INLINE uniqBy #-}
@@ -641,7 +664,9 @@ uniqBy eq = rollingMap f
641664 Nothing -> Just curr
642665 Just x -> if x `eq` curr then Nothing else Just curr
643666
644−-- | Drop repeated elements that are adjacent to each other.
667+-- | See 'uniqBy'.
668+--
669+-- Definition:
645670--
646671-- >>> uniq = Fold.uniqBy (==)
647672--
@@ -676,43 +701,6 @@ repeated :: -- (Monad m, Eq a) =>
676701 Fold m a (Maybe a)
677702repeated = error "Not implemented yet!"
678703
679−-- | Used as a scan. Returns 'Just' for the first occurrence of an element,
680−-- returns 'Nothing' for any other occurrences.
681−--
682−-- >>> stream = Stream.fromList [1::Int,1,2,3,4,4,5,1,5,7]
683−-- >>> Stream.fold Fold.toList $ Stream.mapMaybe id $ Stream.postscan Fold.nub stream
684−-- [1,2,3,4,5,7]
685−--
686−-- /Pre-release/
687−{-# INLINE nub #-}
688−nub :: (Monad m, Ord a) => Fold m a (Maybe a)
689−nub = fmap (\(Tuple' _ x) -> x) $ foldl' step initial
690−
691− where
692−
693− initial = Tuple' Set.empty Nothing
694−
695− step (Tuple' set _) x =
696− if Set.member x set
697− then Tuple' set Nothing
698− else Tuple' (Set.insert x set) (Just x)
699−
700−-- | Like 'nub' but specialized to a stream of 'Int', for better performance.
701−--
702−-- /Pre-release/
703−{-# INLINE nubInt #-}
704−nubInt :: Monad m => Fold m Int (Maybe Int)
705−nubInt = fmap (\(Tuple' _ x) -> x) $ foldl' step initial
706−
707− where
708−
709− initial = Tuple' IntSet.empty Nothing
710−
711− step (Tuple' set _) x =
712− if IntSet.member x set
713− then Tuple' set Nothing
714− else Tuple' (IntSet.insert x set) (Just x)
715−
716704------------------------------------------------------------------------------
717705-- Left folds
718706------------------------------------------------------------------------------
@@ -722,28 +710,40 @@ nubInt = fmap (\(Tuple' _ x) -> x) $ foldl' step initial
722710------------------------------------------------------------------------------
723711
724712-- |
725−-- > drainBy f = lmapM f drain
726−-- > drainBy = Fold.foldMapM (void . f)
713+-- Definitions:
714+--
715+-- >>> drainMapM f = Fold.lmapM f Fold.drain
716+-- >>> drainMapM f = Fold.foldMapM (void . f)
727717--
728718-- Drain all input after passing it through a monadic function. This is the
729719-- dual of mapM_ on stream producers.
730720--
731−-- See also: 'Streamly.Prelude.mapM_'
732−--
721+{-# INLINE drainMapM #-}
722+drainMapM :: Monad m => (a -> m b) -> Fold m a ()
723+drainMapM f = lmapM f drain
724+
725+{-# DEPRECATED drainBy "Please use 'drainMapM' instead." #-}
733726{-# INLINE drainBy #-}
734727drainBy :: Monad m => (a -> m b) -> Fold m a ()
735−drainBy f = lmapM f drain
728+drainBy = drainMapM
736729
737−-- | Extract the last element of the input stream, if any.
730+-- | Returns the latest element of the input stream, if any.
738731--
739−-- > last = fmap getLast $ Fold.foldMap (Last . Just)
732+-- >>> latest = Fold.foldl1' (\_ x -> x)
733+-- >>> latest = fmap getLast $ Fold.foldMap (Last . Just)
740734--
735+{-# INLINE latest #-}
736+latest :: Monad m => Fold m a (Maybe a)
737+latest = foldl1' (\_ x -> x)
738+
739+{-# DEPRECATED last "Please use 'latest' instead." #-}
741740{-# INLINE last #-}
742741last :: Monad m => Fold m a (Maybe a)
743−last = foldl1' (\_ x -> x)
742+last = latest
744743
745−-- | If the stream consists of one or more occurences of the same element
746−-- then returns that element else terminates and returns 'Nothing'.
744+-- | Terminates with 'Nothing' as soon as it finds an element different than
745+-- the previous one, returns 'the' element if the entire input consists of the
746+-- same element.
747747--
748748{-# INLINE the #-}
749749the :: (Monad m, Eq a) => Fold m a (Maybe a)
@@ -765,82 +765,27 @@ the = foldt' step initial id
765765
766766-- | Like 'length', except with a more general 'Num' return value
767767--
768−-- > genericLength = fmap getSum $ foldMap (Sum . const 1)
768+-- Definition:
769+--
770+-- >>> lengthGeneric = fmap getSum $ Fold.foldMap (Sum . const 1)
771+-- >>> lengthGeneric = Fold.foldl' (\n _ -> n + 1) 0
769772--
770773-- /Pre-release/
771−{-# INLINE genericLength #-}
772−genericLength :: (Monad m, Num b) => Fold m a b
773−genericLength = foldl' (\n _ -> n + 1) 0
774+{-# INLINE lengthGeneric #-}
775+lengthGeneric :: (Monad m, Num b) => Fold m a b
776+lengthGeneric = foldl' (\n _ -> n + 1) 0
774777
775778-- | Determine the length of the input stream.
776779--
777−-- > length = fmap getSum $ Fold.foldMap (Sum . const 1)
780+-- Definition:
781+--
782+-- >>> length = Fold.lengthGeneric
783+-- >>> length = fmap getSum $ Fold.foldMap (Sum . const 1)
778784--
779785{-# INLINE length #-}
780786length :: Monad m => Fold m a Int
781−length = genericLength
782−
783−-- XXX Try Hash set
784−-- XXX Add a countDistinct window fold
785−-- XXX Add a bloom filter fold
786−
787−-- | Count non-duplicate elements in the stream.
788−--
789−-- Equivalent to using 'nub' followed by 'length' on a stream.
790−--
791−-- >>> countDistinct = Fold.postscan Fold.nub $ Fold.catMaybes $ Fold.length
792−--
793−-- The memory used is proportional to the number of distinct elements in the
794−-- stream, to guard against using too much memory use it as a scan and
795−-- terminate if the count reaches more than a threshold.
796−--
797−-- /Space/: \(\mathcal{O}(n)\)
798−--
799−-- /Pre-release/
800−--
801−{-# INLINE countDistinct #-}
802−countDistinct :: (Monad m, Ord a) => Fold m a Int
803−countDistinct = postscan nub $ catMaybes length
804−{-
805−countDistinct = fmap (\(Tuple' _ n) -> n) $ foldl' step initial
806−
807− where
787+length = lengthGeneric
808788
809− initial = Tuple' Set.empty 0
810−
811− step (Tuple' set n) x = do
812− if Set.member x set
813− then
814− Tuple' set n
815− else
816− let cnt = n + 1
817− in Tuple' (Set.insert x set) cnt
818−-}
819−
820−-- | Like 'countDistinct' but specialized to a stream of 'Int', for better
821−-- performance.
822−--
823−-- >>> countDistinctInt = Fold.postscan Fold.nubInt $ Fold.catMaybes $ Fold.length
824−--
825−-- /Pre-release/
826−{-# INLINE countDistinctInt #-}
827−countDistinctInt :: Monad m => Fold m Int Int
828−countDistinctInt = postscan nubInt $ catMaybes length
829−{-
830−countDistinctInt = fmap (\(Tuple' _ n) -> n) $ foldl' step initial
831−
832− where
833−
834− initial = Tuple' IntSet.empty 0
835−
836− step (Tuple' set n) x = do
837− if IntSet.member x set
838− then
839− Tuple' set n
840− else
841− let cnt = n + 1
842− in Tuple' (IntSet.insert x set) cnt
843− -}
844789
845790-- | Determine the sum of all elements of a stream of numbers. Returns additive
846791-- identity (@0@) when the stream is empty. Note that this is not numerically
@@ -861,7 +806,9 @@ sum = FoldW.cumulative FoldW.sum
861806-- multiplicative identity (@1@) when the stream is empty. The fold terminates
862807-- when it encounters (@0@) in its input.
863808--
864−-- Compare with @Fold.foldMap Product@.
809+-- Same as the following but terminates on multiplication by @0@:
810+--
811+-- >>> product = fmap Data.Monoid.getProduct $ Fold.foldMap Data.Monoid.Product
865812--
866813{-# INLINE product #-}
867814product :: (Monad m, Num a, Eq a) => Fold m a a
@@ -892,14 +839,17 @@ maximumBy cmp = foldl1' max'
892839 GT -> x
893840 _ -> y
894841
895−-- |
896−-- @
897−-- maximum = Fold.maximumBy compare
898−-- @
842+-- | Determine the maximum element in a stream.
843+--
844+-- Definitions:
899845--
900−-- Determine the maximum element in a stream.
846+-- >>> maximum = Fold.maximumBy compare
847+-- >>> maximum = Fold.foldl1' max
901848--
902−-- Compare with @Fold.foldMap Max@.
849+-- Same as the following but without a default maximum. The 'Max' Monoid uses
850+-- the 'minBound' as the default maximum:
851+--
852+-- >>> maximum = fmap Data.Semigroup.getMax $ Fold.foldMap Data.Semigroup.Max
903853--
904854{-# INLINE maximum #-}
905855maximum :: (Monad m, Ord a) => Fold m a (Maybe a)
@@ -921,11 +871,15 @@ minimumBy cmp = foldl1' min'
921871-- | Determine the minimum element in a stream using the supplied comparison
922872-- function.
923873--
924−-- @
925−-- minimum = 'minimumBy' compare
926−-- @
874+-- Definitions:
875+--
876+-- >>> minimum = Fold.minimumBy compare
877+-- >>> minimum = Fold.foldl1' min
878+--
879+-- Same as the following but without a default minimum. The 'Min' Monoid uses the
880+-- 'maxBound' as the default maximum:
927881--
928−-- Compare with @Fold.foldMap Min@.
882+-- >>> maximum = fmap Data.Semigroup.getMin $ Fold.foldMap Data.Semigroup.Min
929883--
930884{-# INLINE minimum #-}
931885minimum :: (Monad m, Ord a) => Fold m a (Maybe a)
@@ -1011,7 +965,7 @@ defaultSalt = -2578643520546668380
1011965
1012966-- | Compute an 'Int' sized polynomial rolling hash of a stream.
1013967--
1014−-- > rollingHash = Fold.rollingHashWithSalt defaultSalt
968+-- >>> rollingHash = Fold.rollingHashWithSalt Fold.defaultSalt
1015969--
1016970{-# INLINE rollingHash #-}
1017971rollingHash :: (Monad m, Enum a) => Fold m a Int64
@@ -1020,7 +974,7 @@ rollingHash = rollingHashWithSalt defaultSalt
1020974-- | Compute an 'Int' sized polynomial rolling hash of the first n elements of
1021975-- a stream.
1022976--
1023−-- > rollingHashFirstN = Fold.take n Fold.rollingHash
977+-- >>> rollingHashFirstN n = Fold.take n Fold.rollingHash
1024978--
1025979-- /Pre-release/
1026980{-# INLINE rollingHashFirstN #-}
@@ -1053,6 +1007,9 @@ rollingMapM f = Fold step initial extract
10531007
10541008 extract = return . snd
10551009
1010+-- |
1011+-- >>> rollingMap f = Fold.rollingMapM (\x y -> return $ f x y)
1012+--
10561013{-# INLINE rollingMap #-}
10571014rollingMap :: Monad m => (Maybe a -> a -> b) -> Fold m a b
10581015rollingMap f = rollingMapM (\x y -> return $ f x y)
@@ -1061,26 +1018,31 @@ rollingMap f = rollingMapM (\x y -> return $ f x y)
10611018-- Monoidal left folds
10621019------------------------------------------------------------------------------
10631020
1064−-- | Append the elements of an input stream to a provided starting value.
1021+-- | Semigroup concat. Append the elements of an input stream to a provided
1022+-- starting value.
10651023--
1066−-- >>> Stream.fold (Fold.sconcat 10) (fmap Data.Monoid.Sum $ Stream.enumerateFromTo 1 10)
1067−-- Sum {getSum = 65}
1024+-- Definition:
10681025--
1069−-- @
1070−-- sconcat = Fold.foldl' (<>)
1071−-- @
1026+-- >>> sconcat = Fold.foldl' (<>)
1027+--
1028+-- >>> semigroups = fmap Data.Monoid.Sum $ Stream.enumerateFromTo 1 10
1029+-- >>> Stream.fold (Fold.sconcat 10) semigroups
1030+-- Sum {getSum = 65}
10721031--
10731032{-# INLINE sconcat #-}
10741033sconcat :: (Monad m, Semigroup a) => a -> Fold m a a
10751034sconcat = foldl' (<>)
10761035
1077−-- | Fold an input stream consisting of monoidal elements using 'mappend'
1078−-- and 'mempty'.
1036+-- | Monoid concat. Fold an input stream consisting of monoidal elements using
1037+-- 'mappend' and 'mempty'.
10791038--
1080−-- >>> Stream.fold Fold.mconcat (fmap Data.Monoid.Sum $ Stream.enumerateFromTo 1 10)
1081−-- Sum {getSum = 55}
1039+-- Definition:
10821040--
1083−-- > mconcat = Fold.sconcat mempty
1041+-- >>> mconcat = Fold.sconcat mempty
1042+--
1043+-- >>> monoids = fmap Data.Monoid.Sum $ Stream.enumerateFromTo 1 10
1044+-- >>> Stream.fold Fold.mconcat monoids
1045+-- Sum {getSum = 55}
10841046--
10851047{-# INLINE mconcat #-}
10861048mconcat ::
@@ -1089,26 +1051,31 @@ mconcat ::
10891051mconcat = sconcat mempty
10901052
10911053-- |
1092−-- > foldMap f = Fold.lmap f Fold.mconcat
1054+-- Definition:
1055+--
1056+-- >>> foldMap f = Fold.lmap f Fold.mconcat
10931057--
10941058-- Make a fold from a pure function that folds the output of the function
10951059-- using 'mappend' and 'mempty'.
10961060--
1097−-- >>> Stream.fold (Fold.foldMap Data.Monoid.Sum) $ Stream.enumerateFromTo 1 10
1061+-- >>> sum = Fold.foldMap Data.Monoid.Sum
1062+-- >>> Stream.fold sum $ Stream.enumerateFromTo 1 10
10981063-- Sum {getSum = 55}
10991064--
11001065{-# INLINE foldMap #-}
1101−foldMap :: (Monad m, Monoid b
1102− ) => (a -> b) -> Fold m a b
1066+foldMap :: (Monad m, Monoid b) => (a -> b) -> Fold m a b
11031067foldMap f = lmap f mconcat
11041068
11051069-- |
1106−-- > foldMapM f = Fold.lmapM f Fold.mconcat
1070+-- Definition:
1071+--
1072+-- >>> foldMapM f = Fold.lmapM f Fold.mconcat
11071073--
11081074-- Make a fold from a monadic function that folds the output of the function
11091075-- using 'mappend' and 'mempty'.
11101076--
1111−-- >>> Stream.fold (Fold.foldMapM (return . Data.Monoid.Sum)) $ Stream.enumerateFromTo 1 10
1077+-- >>> sum = Fold.foldMapM (return . Data.Monoid.Sum)
1078+-- >>> Stream.fold sum $ Stream.enumerateFromTo 1 10
11121079-- Sum {getSum = 55}
11131080--
11141081{-# INLINE foldMapM #-}
@@ -1131,7 +1098,9 @@ foldMapM act = foldlM' step (pure mempty)
11311098
11321099-- | Buffers the input stream to a list in the reverse order of the input.
11331100--
1134−-- > toListRev = Fold.foldl' (flip (:)) []
1101+-- Definition:
1102+--
1103+-- >>> toListRev = Fold.foldl' (flip (:)) []
11351104--
11361105-- /Warning!/ working on large lists accumulated as buffers in memory could be
11371106-- very inefficient, consider using "Streamly.Array" instead.
@@ -1149,7 +1118,9 @@ toListRev = foldl' (flip (:)) []
11491118-- | A fold that drains the first n elements of its input, running the effects
11501119-- and discarding the results.
11511120--
1152−-- > drainN n = Fold.take n Fold.drain
1121+-- Definition:
1122+--
1123+-- >>> drainN n = Fold.take n Fold.drain
11531124--
11541125-- /Pre-release/
11551126{-# INLINE drainN #-}
@@ -1163,9 +1134,9 @@ drainN n = take n drain
11631134-- | Like 'index', except with a more general 'Integral' argument
11641135--
11651136-- /Pre-release/
1166−{-# INLINE genericIndex #-}
1167−genericIndex :: (Integral i, Monad m) => i -> Fold m a (Maybe a)
1168−genericIndex i = foldt' step (Partial 0) (const Nothing)
1137+{-# INLINE indexGeneric #-}
1138+indexGeneric :: (Integral i, Monad m) => i -> Fold m a (Maybe a)
1139+indexGeneric i = foldt' step (Partial 0) (const Nothing)
11691140
11701141 where
11711142
@@ -1174,15 +1145,18 @@ genericIndex i = foldt' step (Partial 0) (const Nothing)
11741145 then Done $ Just a
11751146 else Partial (j + 1)
11761147
1177−-- | Lookup the element at the given index.
1148+-- | Return the element at the given index.
11781149--
1179−-- See also: 'Streamly.Prelude.!!'
1150+-- Definition:
1151+--
1152+-- >>> index = Fold.indexGeneric
11801153--
11811154{-# INLINE index #-}
11821155index :: Monad m => Int -> Fold m a (Maybe a)
1183−index = genericIndex
1156+index = indexGeneric
11841157
1185−-- | Map a 'Maybe' returning function on the next element in the stream.
1158+-- | Consume a single input and transform it using the supplied 'Maybe'
1159+-- returning function.
11861160--
11871161-- /Pre-release/
11881162--
@@ -1190,16 +1164,24 @@ index = genericIndex
11901164maybe :: Monad m => (a -> Maybe b) -> Fold m a (Maybe b)
11911165maybe f = foldt' (const (Done . f)) (Partial Nothing) id
11921166
1193−-- | Test if the next element satisfies the supplied predicate.
1167+-- | Consume a single element and return it if it passes the predicate else
1168+-- return 'Nothing'.
1169+--
1170+-- Definition:
1171+--
1172+-- >>> satisfy f = Fold.maybe (\a -> if f a then Just a else Nothing)
11941173--
11951174-- /Pre-release/
11961175{-# INLINE satisfy #-}
11971176satisfy :: Monad m => (a -> Bool) -> Fold m a (Maybe a)
1177+satisfy f = maybe (\a -> if f a then Just a else Nothing)
1178+{-
11981179satisfy f = Fold step (return $ Partial ()) (const (return Nothing))
11991180
12001181 where
12011182
12021183 step () a = return $ Done $ if f a then Just a else Nothing
1184+-}
12031185
12041186-- Naming notes:
12051187--
@@ -1228,6 +1210,12 @@ satisfy f = Fold step (return $ Partial ()) (const (return Nothing))
12281210
12291211-- | Take one element from the stream and stop.
12301212--
1213+-- Definition:
1214+--
1215+-- >>> one = Fold.maybe Just
1216+--
1217+-- This is similar to the stream 'Stream.uncons' operation.
1218+--
12311219{-# INLINE one #-}
12321220one :: Monad m => Fold m a (Maybe a)
12331221one = maybe Just
@@ -1266,7 +1254,9 @@ find p = findM (return . p)
12661254-- | In a stream of (key-value) pairs @(a, b)@, return the value @b@ of the
12671255-- first pair where the key equals the given value @a@.
12681256--
1269−-- > lookup = snd <$> Fold.find ((==) . fst)
1257+-- Definition:
1258+--
1259+-- >>> lookup x = fmap snd <$> Fold.find ((== x) . fst)
12701260--
12711261{-# INLINE lookup #-}
12721262lookup :: (Eq a, Monad m) => a -> Fold m (a,b) (Maybe b)
@@ -1292,10 +1282,8 @@ findIndex predicate = foldt' step (Partial 0) (const Nothing)
12921282 then Done $ Just i
12931283 else Partial (i + 1)
12941284
1295−-- XXX If we have a Continue contructor we won't need a Maybe type for the
1296−-- scan. The fold will have the ability to skip producing output.
1297−--
1298−-- | Returns all indices that satisfy the given predicate.
1285+-- | Returns the index of the latest element if the element satisfies the given
1286+-- predicate.
12991287--
13001288{-# INLINE findIndices #-}
13011289findIndices :: Monad m => (a -> Bool) -> Fold m a (Maybe Int)
@@ -1310,8 +1298,10 @@ findIndices predicate =
13101298 then Right (either id id i + 1)
13111299 else Left (either id id i + 1)
13121300
1313−-- | Find all the indices where the value of the element in the stream is equal
1314−-- to the given value.
1301+-- | Returns the index of the latest element if the element matches the given
1302+-- value.
1303+--
1304+-- Definition:
13151305--
13161306-- >>> elemIndices a = Fold.findIndices (== a)
13171307--
@@ -1321,31 +1311,46 @@ elemIndices a = findIndices (== a)
13211311
13221312-- | Returns the first index where a given value is found in the stream.
13231313--
1324−-- > elemIndex a = Fold.findIndex (== a)
1314+-- Definition:
1315+--
1316+-- >>> elemIndex a = Fold.findIndex (== a)
13251317--
13261318{-# INLINE elemIndex #-}
13271319elemIndex :: (Eq a, Monad m) => a -> Fold m a (Maybe Int)
1328−elemIndex a = findIndex (a ==)
1320+elemIndex a = findIndex (== a)
13291321
13301322------------------------------------------------------------------------------
13311323-- To Boolean
13321324------------------------------------------------------------------------------
13331325
1334−-- | Return 'True' if the input stream is empty.
1326+-- Similar to 'eof' parser, but the fold consumes and discards an input element
1327+-- when not at eof. XXX Remove or Rename to "eof"?
1328+
1329+-- | Consume one element, return 'True' if successful else return 'False'. In
1330+-- other words, test if the input is empty or not.
13351331--
1336−-- > null = fmap isJust Fold.one
1332+-- WARNING! It consumes one element if the stream is not empty. If that is not
1333+-- what you want please use the eof parser instead.
1334+--
1335+-- Definition:
1336+--
1337+-- >>> null = fmap isJust Fold.one
13371338--
13381339{-# INLINE null #-}
13391340null :: Monad m => Fold m a Bool
13401341null = foldt' (\() _ -> Done False) (Partial ()) (const True)
13411342
1342−-- | Returns 'True' if any of the elements of a stream satisfies a predicate.
1343+-- | Returns 'True' if any element of the input satisfies the predicate.
1344+--
1345+-- Definition:
1346+--
1347+-- >>> any p = Fold.lmap p Fold.or
1348+--
1349+-- Example:
13431350--
13441351-- >>> Stream.fold (Fold.any (== 0)) $ Stream.fromList [1,0,1]
13451352-- True
13461353--
1347−-- > any p = Fold.lmap p Fold.or
1348−--
13491354{-# INLINE any #-}
13501355any :: Monad m => (a -> Bool) -> Fold m a Bool
13511356any predicate = foldt' step initial id
@@ -1361,19 +1366,25 @@ any predicate = foldt' step initial id
13611366
13621367-- | Return 'True' if the given element is present in the stream.
13631368--
1364−-- > elem a = Fold.any (== a)
1369+-- Definition:
1370+--
1371+-- >>> elem a = Fold.any (== a)
13651372--
13661373{-# INLINE elem #-}
13671374elem :: (Eq a, Monad m) => a -> Fold m a Bool
1368−elem a = any (a ==)
1375+elem a = any (== a)
13691376
1370−-- | Returns 'True' if all elements of a stream satisfy a predicate.
1377+-- | Returns 'True' if all elements of the input satisfy the predicate.
1378+--
1379+-- Definition:
1380+--
1381+-- >>> all p = Fold.lmap p Fold.and
1382+--
1383+-- Example:
13711384--
13721385-- >>> Stream.fold (Fold.all (== 0)) $ Stream.fromList [1,0,1]
13731386-- False
13741387--
1375−-- > all p = Fold.lmap p Fold.and
1376−--
13771388{-# INLINE all #-}
13781389all :: Monad m => (a -> Bool) -> Fold m a Bool
13791390all predicate = foldt' step initial id
@@ -1389,15 +1400,19 @@ all predicate = foldt' step initial id
13891400
13901401-- | Returns 'True' if the given element is not present in the stream.
13911402--
1392−-- > notElem a = Fold.all (/= a)
1403+-- Definition:
1404+--
1405+-- >>> notElem a = Fold.all (/= a)
13931406--
13941407{-# INLINE notElem #-}
13951408notElem :: (Eq a, Monad m) => a -> Fold m a Bool
1396−notElem a = all (a /=)
1409+notElem a = all (/= a)
13971410
13981411-- | Returns 'True' if all elements are 'True', 'False' otherwise
13991412--
1400−-- > and = Fold.all (== True)
1413+-- Definition:
1414+--
1415+-- >>> and = Fold.all (== True)
14011416--
14021417{-# INLINE and #-}
14031418and :: Monad m => Fold m Bool Bool
@@ -1405,7 +1420,9 @@ and = all (== True)
14051420
14061421-- | Returns 'True' if any element is 'True', 'False' otherwise
14071422--
1408−-- > or = Fold.any (== True)
1423+-- Definition:
1424+--
1425+-- >>> or = Fold.any (== True)
14091426--
14101427{-# INLINE or #-}
14111428or :: Monad m => Fold m Bool Bool
@@ -1531,6 +1548,9 @@ droppingWhileM p = Fold step initial (return . toMaybe)
15311548 else Just' a
15321549 step _ a = return $ Partial $ Just' a
15331550
1551+-- |
1552+-- >>> droppingWhile p = Fold.droppingWhileM (return . p)
1553+--
15341554{-# INLINE droppingWhile #-}
15351555droppingWhile :: Monad m => (a -> Bool) -> Fold m a (Maybe a)
15361556droppingWhile p = droppingWhileM (return . p)
@@ -1539,20 +1559,20 @@ droppingWhile p = droppingWhileM (return . p)
15391559-- S.splitOn in favor of the fold.
15401560--
15411561-- XXX Use Fold.many instead once it is fixed.
1542−--
1562+-- > Stream.splitOnSuffix p f = Stream.foldMany (Fold.takeEndBy_ p f)
1563+
15431564-- | Like 'takeEndBy' but drops the element on which the predicate succeeds.
15441565--
1545−-- >>> Stream.fold (Fold.takeEndBy_ (== '\n') Fold.toList) $ Stream.fromList "hello\nthere\n"
1566+-- Example:
1567+--
1568+-- >>> input = Stream.fromList "hello\nthere\n"
1569+-- >>> line = Fold.takeEndBy_ (== '\n') Fold.toList
1570+-- >>> Stream.fold line input
15461571-- "hello"
15471572--
1548−-- >>> Stream.fold Fold.toList $ Stream.foldMany (Fold.takeEndBy_ (== '\n') Fold.toList) $ Stream.fromList "hello\nthere\n"
1573+-- >>> Stream.fold Fold.toList $ Stream.foldMany line input
15491574-- ["hello","there"]
15501575--
1551−-- > Stream.splitOnSuffix p f = Stream.foldMany (Fold.takeEndBy_ p f)
1552−--
1553−-- See 'Streamly.Prelude.splitOnSuffix' for more details on splitting a
1554−-- stream using 'takeEndBy_'.
1555−--
15561576{-# INLINE takeEndBy_ #-}
15571577takeEndBy_ :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a b
15581578-- takeEndBy_ predicate = scanMaybe (takingEndBy_ predicate)
@@ -1566,20 +1586,22 @@ takeEndBy_ predicate (Fold fstep finitial fextract) =
15661586 then fstep s a
15671587 else Done <$> fextract s
15681588
1589+-- Note:
1590+-- > Stream.splitWithSuffix p f = Stream.foldMany (Fold.takeEndBy p f)
1591+
15691592-- | Take the input, stop when the predicate succeeds taking the succeeding
15701593-- element as well.
15711594--
1572−-- >>> Stream.fold (Fold.takeEndBy (== '\n') Fold.toList) $ Stream.fromList "hello\nthere\n"
1595+-- Example:
1596+--
1597+-- >>> input = Stream.fromList "hello\nthere\n"
1598+-- >>> line = Fold.takeEndBy (== '\n') Fold.toList
1599+-- >>> Stream.fold line input
15731600-- "hello\n"
15741601--
1575−-- >>> Stream.fold Fold.toList $ Stream.foldMany (Fold.takeEndBy (== '\n') Fold.toList) $ Stream.fromList "hello\nthere\n"
1602+-- >>> Stream.fold Fold.toList $ Stream.foldMany line input
15761603-- ["hello\n","there\n"]
15771604--
1578−-- > Stream.splitWithSuffix p f = Stream.foldMany (Fold.takeEndBy p f)
1579−--
1580−-- See 'Streamly.Prelude.splitWithSuffix' for more details on splitting a
1581−-- stream using 'takeEndBy'.
1582−--
15831605{-# INLINE takeEndBy #-}
15841606takeEndBy :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a b
15851607-- takeEndBy predicate = scanMaybe (takingEndBy predicate)
@@ -1924,10 +1946,16 @@ takeEndBySeq_ patArr (Fold fstep finitial fextract) =
19241946-- ---stream m a---| |---m (b,c)
19251947-- |-------Fold m a c--------|
19261948-- @
1927−-- >>> Stream.fold (Fold.tee Fold.sum Fold.length) (Stream.enumerateFromTo 1.0 100.0)
1928−-- (5050.0,100)
19291949--
1930−-- > tee = teeWith (,)
1950+-- Definition:
1951+--
1952+-- >>> tee = Fold.teeWith (,)
1953+--
1954+-- Example:
1955+--
1956+-- >>> t = Fold.tee Fold.sum Fold.length
1957+-- >>> Stream.fold t (Stream.enumerateFromTo 1.0 100.0)
1958+-- (5050.0,100)
19311959--
19321960{-# INLINE tee #-}
19331961tee :: Monad m => Fold m a b -> Fold m a c -> Fold m a (b,c)
@@ -1992,16 +2020,15 @@ partitionByMUsing t f fld1 fld2 =
19922020-- |-------Fold c y--------|
19932021-- @
19942022--
1995−-- Send input to either fold randomly:
2023+-- Example, send input to either fold randomly:
19962024--
1997−-- @
1998−-- > import System.Random (randomIO)
1999−-- > randomly a = randomIO >>= \\x -> return $ if x then Left a else Right a
2000−-- > Stream.fold (Fold.partitionByM randomly Fold.length Fold.length) (Stream.enumerateFromTo 1 100)
2001−-- (59,41)
2002−-- @
2025+-- >>> import System.Random (randomIO)
2026+-- >>> randomly a = randomIO >>= \x -> return $ if x then Left a else Right a
2027+-- >>> f = Fold.partitionByM randomly Fold.length Fold.length
2028+-- >>> Stream.fold f (Stream.enumerateFromTo 1 100)
2029+-- ...
20032030--
2004−-- Send input to the two folds in a proportion of 2:1:
2031+-- Example, send input to the two folds in a proportion of 2:1:
20052032--
20062033-- >>> :{
20072034-- proportionately m n = do
@@ -2014,8 +2041,9 @@ partitionByMUsing t f fld1 fld2 =
20142041--
20152042-- >>> :{
20162043-- main = do
2017−-- f <- proportionately 2 1
2018−-- r <- Stream.fold (Fold.partitionByM f Fold.length Fold.length) (Stream.enumerateFromTo (1 :: Int) 100)
2044+-- g <- proportionately 2 1
2045+-- let f = Fold.partitionByM g Fold.length Fold.length
2046+-- r <- Stream.fold f (Stream.enumerateFromTo (1 :: Int) 100)
20192047-- print r
20202048-- :}
20212049--
@@ -2040,7 +2068,6 @@ partitionByM = partitionByMUsing teeWith
20402068
20412069-- | Similar to 'partitionByM' but terminates when the first fold terminates.
20422070--
2043−--
20442071{-# INLINE partitionByFstM #-}
20452072partitionByFstM :: Monad m
20462073 => (a -> m (Either b c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)
@@ -2048,7 +2075,6 @@ partitionByFstM = partitionByMUsing teeWithFst
20482075
20492076-- | Similar to 'partitionByM' but terminates when any fold terminates.
20502077--
2051−--
20522078{-# INLINE partitionByMinM #-}
20532079partitionByMinM :: Monad m =>
20542080 (a -> m (Either b c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)
@@ -2057,9 +2083,10 @@ partitionByMinM = partitionByMUsing teeWithMin
20572083-- Note: we could use (a -> Bool) instead of (a -> Either b c), but the latter
20582084-- makes the signature clearer as to which case belongs to which fold.
20592085-- XXX need to check the performance in both cases.
2086+
20602087-- | Same as 'partitionByM' but with a pure partition function.
20612088--
2062−-- Count even and odd numbers in a stream:
2089+-- Example, count even and odd numbers in a stream:
20632090--
20642091-- >>> :{
20652092-- let f = Fold.partitionBy (\n -> if even n then Left n else Right n)
@@ -2079,7 +2106,9 @@ partitionBy f = partitionByM (return . f)
20792106-- and routes the 'Left' values to the first fold and 'Right' values to the
20802107-- second fold.
20812108--
2082−-- > partition = partitionBy id
2109+-- Definition:
2110+--
2111+-- >>> partition = Fold.partitionBy id
20832112--
20842113{-# INLINE partition #-}
20852114partition :: Monad m
@@ -2094,412 +2123,6 @@ partition = partitionBy id
20942123-- partitionN fs = Fold step begin done
20952124-}
20962125
2097−------------------------------------------------------------------------------
2098−-- demux: in a key value stream fold each key sub-stream with a different fold
2099−------------------------------------------------------------------------------
2100−
2101−-- TODO Demultiplex an input element into a number of typed variants. We want
2102−-- to statically restrict the target values within a set of predefined types,
2103−-- an enumeration of a GADT.
2104−--
2105−-- This is the consumer side dual of the producer side 'mux' operation (XXX to
2106−-- be implemented).
2107−--
2108−-- XXX If we use Refold in it, it can perhaps fuse/be more efficient. For
2109−-- example we can store just the result rather than storing the whole fold in
2110−-- the Map.
2111−--
2112−-- Note: There are separate functions to determine Key and Fold from the input
2113−-- because key is to be determined on each input whereas fold is to be
2114−-- determined only once for a key.
2115−
2116−-- | In a key value stream, fold values corresponding to each key with a key
2117−-- specific fold. The fold returns the fold result as the second component of
2118−-- the output tuple whenever a fold terminates. The first component of the
2119−-- tuple is a container of in-progress folds. If a fold terminates, another
2120−-- instance of the fold is started upon receiving an input with that key.
2121−--
2122−-- This can be used to scan a stream and collect the results from the scan
2123−-- output.
2124−--
2125−-- /Pre-release/
2126−--
2127−{-# INLINE demuxScanWith #-}
2128−demuxScanWith :: (Monad m, IsMap f, Traversable f) =>
2129− (a -> Key f)
2130− -> (a -> m (Fold m a b))
2131− -> Fold m a (m (f b), Maybe (Key f, b))
2132−demuxScanWith getKey getFold = fmap extract $ foldlM' step initial
2133−
2134− where
2135−
2136− initial = return $ Tuple' IsMap.mapEmpty Nothing
2137−
2138− {-# INLINE runFold #-}
2139− runFold kv (Fold step1 initial1 extract1) (k, a) = do
2140− res <- initial1
2141− case res of
2142− Partial s -> do
2143− res1 <- step1 s a
2144− return
2145− $ case res1 of
2146− Partial _ ->
2147− let fld = Fold step1 (return res1) extract1
2148− in Tuple' (IsMap.mapInsert k fld kv) Nothing
2149− Done b -> Tuple' (IsMap.mapDelete k kv) (Just (k, b))
2150− Done b -> return $ Tuple' kv (Just (k, b))
2151−
2152− step (Tuple' kv _) a = do
2153− let k = getKey a
2154− case IsMap.mapLookup k kv of
2155− Nothing -> do
2156− fld <- getFold a
2157− runFold kv fld (k, a)
2158− Just f -> runFold kv f (k, a)
2159−
2160− extract (Tuple' kv x) = (Prelude.mapM f kv, x)
2161−
2162− where
2163−
2164− f (Fold _ i e) = do
2165− r <- i
2166− case r of
2167− Partial s -> e s
2168− Done b -> return b
2169−
2170−-- | This is specialized version of 'demuxScanWith' that uses mutable cells as
2171−-- fold accumulators for better performance.
2172−--
2173−-- >>> demuxScanMutWith = Fold.demuxScanWith
2174−--
2175−{-# INLINE demuxScanMutWith #-}
2176−demuxScanMutWith :: (MonadIO m, IsMap f, Traversable f) =>
2177− (a -> Key f)
2178− -> (a -> m (Fold m a b))
2179− -> Fold m a (m (f b), Maybe (Key f, b))
2180−demuxScanMutWith getKey getFold = fmap extract $ foldlM' step initial
2181−
2182− where
2183−
2184− initial = return $ Tuple' IsMap.mapEmpty Nothing
2185−
2186− {-# INLINE initFold #-}
2187− initFold kv (Fold step1 initial1 extract1) (k, a) = do
2188− res <- initial1
2189− case res of
2190− Partial s -> do
2191− res1 <- step1 s a
2192− case res1 of
2193− Partial _ -> do
2194− let fld = Fold step1 (return res1) extract1
2195− ref <- liftIO $ newIORef fld
2196− return $ Tuple' (IsMap.mapInsert k ref kv) Nothing
2197− Done b -> return $ Tuple' kv (Just (k, b))
2198− Done b -> return $ Tuple' kv (Just (k, b))
2199−
2200− {-# INLINE runFold #-}
2201− runFold kv ref (Fold step1 initial1 extract1) (k, a) = do
2202− res <- initial1
2203− case res of
2204− Partial s -> do
2205− res1 <- step1 s a
2206− case res1 of
2207− Partial _ -> do
2208− let fld = Fold step1 (return res1) extract1
2209− liftIO $ writeIORef ref fld
2210− return $ Tuple' kv Nothing
2211− Done b ->
2212− let kv1 = IsMap.mapDelete k kv
2213− in return $ Tuple' kv1 (Just (k, b))
2214− Done _ -> error "demuxScanMutWith: unreachable"
2215−
2216− step (Tuple' kv _) a = do
2217− let k = getKey a
2218− case IsMap.mapLookup k kv of
2219− Nothing -> do
2220− f <- getFold a
2221− initFold kv f (k, a)
2222− Just ref -> do
2223− f <- liftIO $ readIORef ref
2224− runFold kv ref f (k, a)
2225−
2226− extract (Tuple' kv x) = (Prelude.mapM f kv, x)
2227−
2228− where
2229−
2230− f ref = do
2231− (Fold _ i e) <- liftIO $ readIORef ref
2232− r <- i
2233− case r of
2234− Partial s -> e s
2235− Done b -> return b
2236−
2237−-- | This collects all the results of 'demuxScanWith' in a container.
2238−--
2239−{-# INLINE demuxWith #-}
2240−demuxWith :: (Monad m, IsMap f, Traversable f) =>
2241− (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (f b)
2242−demuxWith getKey getFold =
2243− let
2244− classifier = demuxScanWith getKey getFold
2245− getMap Nothing = pure IsMap.mapEmpty
2246− getMap (Just action) = action
2247− aggregator =
2248− teeWith IsMap.mapUnion
2249− (rmapM getMap $ lmap fst last)
2250− (lmap snd $ catMaybes toMap)
2251− in postscan classifier aggregator
2252−
2253−-- | Same as 'demuxWith' but uses 'demuxScanMutWith' for better performance.
2254−--
2255−-- >>> demuxMutWith = Fold.demuxWith
2256−--
2257−{-# INLINE demuxMutWith #-}
2258−demuxMutWith :: (MonadIO m, IsMap f, Traversable f) =>
2259− (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (f b)
2260−demuxMutWith getKey getFold =
2261− let
2262− classifier = demuxScanMutWith getKey getFold
2263− getMap Nothing = pure IsMap.mapEmpty
2264− getMap (Just action) = action
2265− aggregator =
2266− teeWith IsMap.mapUnion
2267− (rmapM getMap $ lmap fst last)
2268− (lmap snd $ catMaybes toMap)
2269− in postscan classifier aggregator
2270−
2271−-- | Fold a stream of key value pairs using a function that maps keys to folds.
2272−--
2273−-- >>> import Data.Map (Map)
2274−-- >>> :{
2275−-- let f "SUM" = return Fold.sum
2276−-- f _ = return Fold.product
2277−-- input = Stream.fromList [("SUM",1),("PRODUCT",2),("SUM",3),("PRODUCT",4)]
2278−-- in Stream.fold (Fold.demux f) input :: IO (Map String Int)
2279−-- :}
2280−-- fromList [("PRODUCT",8),("SUM",4)]
2281−--
2282−-- >>> demux f = Fold.demuxWith fst (Fold.lmap snd . f)
2283−--
2284−-- /Pre-release/
2285−{-# INLINE demux #-}
2286−demux :: (Monad m, IsMap f, Traversable f) =>
2287− (Key f -> m (Fold m a b)) -> Fold m (Key f, a) (f b)
2288−demux f = demuxWith fst (\(k, _) -> fmap (lmap snd) (f k))
2289−
2290−------------------------------------------------------------------------------
2291−-- Classify: Like demux but uses the same fold for all keys.
2292−------------------------------------------------------------------------------
2293−
2294−-- XXX Change these to make the behavior similar to demux* variants. We can
2295−-- implement this using classifyScanManyWith. Maintain a set of done folds in
2296−-- the underlying monad, and when initial is called look it up, if the fold is
2297−-- done then initial would set a flag in the state to ignore the input or
2298−-- return an error.
2299−
2300−-- | Folds the values for each key using the supplied fold. When scanning, as
2301−-- soon as the fold is complete, its result is available in the second
2302−-- component of the tuple. The first component of the tuple is a snapshot of
2303−-- the in-progress folds.
2304−--
2305−-- Once the fold for a key is done, any future values of the key are ignored.
2306−--
2307−-- >>> classifyScanWith f fld = Fold.demuxScanWith f (const fld)
2308−--
2309−{-# INLINE classifyScanWith #-}
2310−classifyScanWith :: (Monad m, IsMap f, Traversable f, Ord (Key f)) =>
2311− -- Note: we need to return the Map itself to display the in-progress values
2312− -- e.g. to implement top. We could possibly create a separate abstraction
2313− -- for that use case. We return an action because we want it to be lazy so
2314− -- that the downstream consumers can choose to process or discard it.
2315− (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))
2316−classifyScanWith f (Fold step1 initial1 extract1) =
2317− fmap extract $ foldlM' step initial
2318−
2319− where
2320−
2321− initial = return $ Tuple3' IsMap.mapEmpty Set.empty Nothing
2322−
2323− {-# INLINE initFold #-}
2324− initFold kv set k a = do
2325− x <- initial1
2326− case x of
2327− Partial s -> do
2328− r <- step1 s a
2329− return
2330− $ case r of
2331− Partial s1 ->
2332− Tuple3' (IsMap.mapInsert k s1 kv) set Nothing
2333− Done b ->
2334− Tuple3' kv set (Just (k, b))
2335− Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b)))
2336−
2337− step (Tuple3' kv set _) a = do
2338− let k = f a
2339− case IsMap.mapLookup k kv of
2340− Nothing -> do
2341− if Set.member k set
2342− then return (Tuple3' kv set Nothing)
2343− else initFold kv set k a
2344− Just s -> do
2345− r <- step1 s a
2346− return
2347− $ case r of
2348− Partial s1 ->
2349− Tuple3' (IsMap.mapInsert k s1 kv) set Nothing
2350− Done b ->
2351− let kv1 = IsMap.mapDelete k kv
2352− in Tuple3' kv1 (Set.insert k set) (Just (k, b))
2353−
2354− extract (Tuple3' kv _ x) = (Prelude.mapM extract1 kv, x)
2355−
2356−-- XXX we can use a Prim IORef if we can constrain the state "s" to be Prim
2357−--
2358−-- The code is almost the same as classifyScanWith except the IORef operations.
2359−--
2360−-- | Same as classifyScanWith except that it uses mutable IORef cells in the
2361−-- Map providing better performance. Be aware that if this is used as a scan,
2362−-- the values in the intermediate Maps would be mutable.
2363−--
2364−-- >>> classifyScanMutWith = classifyScanWith
2365−-- >>> classifyScanMutWith f fld = demuxScanMutWith f (const fld)
2366−--
2367−{-# INLINE classifyScanMutWith #-}
2368−classifyScanMutWith :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>
2369− (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))
2370−classifyScanMutWith f (Fold step1 initial1 extract1) =
2371− fmap extract $ foldlM' step initial
2372−
2373− where
2374−
2375− initial = return $ Tuple3' IsMap.mapEmpty Set.empty Nothing
2376−
2377− {-# INLINE initFold #-}
2378− initFold kv set k a = do
2379− x <- initial1
2380− case x of
2381− Partial s -> do
2382− r <- step1 s a
2383− case r of
2384− Partial s1 -> do
2385− ref <- liftIO $ newIORef s1
2386− return $ Tuple3' (IsMap.mapInsert k ref kv) set Nothing
2387− Done b ->
2388− return $ Tuple3' kv set (Just (k, b))
2389− Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b)))
2390−
2391− step (Tuple3' kv set _) a = do
2392− let k = f a
2393− case IsMap.mapLookup k kv of
2394− Nothing -> do
2395− if Set.member k set
2396− then return (Tuple3' kv set Nothing)
2397− else initFold kv set k a
2398− Just ref -> do
2399− s <- liftIO $ readIORef ref
2400− r <- step1 s a
2401− case r of
2402− Partial s1 -> do
2403− liftIO $ writeIORef ref s1
2404− return $ Tuple3' kv set Nothing
2405− Done b ->
2406− let kv1 = IsMap.mapDelete k kv
2407− in return
2408− $ Tuple3' kv1 (Set.insert k set) (Just (k, b))
2409−
2410− extract (Tuple3' kv _ x) =
2411− (Prelude.mapM (\ref -> liftIO (readIORef ref) >>= extract1) kv, x)
2412−
2413−-- | Fold a key value stream to a key-value container. If the same key appears
2414−-- multiple times, only the last value is retained.
2415−{-# INLINE toMap #-}
2416−toMap :: (Monad m, IsMap f) => Fold m (Key f, a) (f a)
2417−toMap = foldl' (\kv (k, v) -> IsMap.mapInsert k v kv) IsMap.mapEmpty
2418−
2419−-- | Split the input stream based on a key field and fold each split using the
2420−-- given fold. Useful for map/reduce, bucketizing the input in different bins
2421−-- or for generating histograms.
2422−--
2423−-- >>> import Data.Map.Strict (Map)
2424−-- >>> :{
2425−-- let input = Stream.fromList [("ONE",1),("ONE",1.1),("TWO",2), ("TWO",2.2)]
2426−-- classify = Fold.classifyWith fst (Fold.lmap snd Fold.toList)
2427−-- in Stream.fold classify input :: IO (Map String [Double])
2428−-- :}
2429−-- fromList [("ONE",[1.0,1.1]),("TWO",[2.0,2.2])]
2430−--
2431−-- Once the classifier fold terminates for a particular key any further inputs
2432−-- in that bucket are ignored.
2433−--
2434−-- Space used is proportional to the number of keys seen till now and
2435−-- monotonically increases because it stores whether a key has been seen or
2436−-- not.
2437−--
2438−-- /Stops: never/
2439−--
2440−-- /Pre-release/
2441−--
2442−{-# INLINE classifyWith #-}
2443−classifyWith :: (Monad m, IsMap f, Traversable f, Ord (Key f)) =>
2444− (a -> Key f) -> Fold m a b -> Fold m a (f b)
2445−classifyWith f fld =
2446− let
2447− classifier = classifyScanWith f fld
2448− getMap Nothing = pure IsMap.mapEmpty
2449− getMap (Just action) = action
2450− aggregator =
2451− teeWith IsMap.mapUnion
2452− (rmapM getMap $ lmap fst last)
2453− (lmap snd $ catMaybes toMap)
2454− in postscan classifier aggregator
2455−
2456−-- | Same as 'classifyWith' but maybe faster because it uses mutable cells as
2457−-- fold accumulators in the Map.
2458−--
2459−-- >>> classifyMutWith = Fold.classifyWith
2460−--
2461−{-# INLINE classifyMutWith #-}
2462−classifyMutWith :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>
2463− (a -> Key f) -> Fold m a b -> Fold m a (f b)
2464−classifyMutWith f fld =
2465− let
2466− classifier = classifyScanMutWith f fld
2467− getMap Nothing = pure IsMap.mapEmpty
2468− getMap (Just action) = action
2469− aggregator =
2470− teeWith IsMap.mapUnion
2471− (rmapM getMap $ lmap fst last)
2472− (lmap snd $ catMaybes toMap)
2473− in postscan classifier aggregator
2474−
2475−-- | Given an input stream of key value pairs and a fold for values, fold all
2476−-- the values belonging to each key. Useful for map/reduce, bucketizing the
2477−-- input in different bins or for generating histograms.
2478−--
2479−-- >>> :{
2480−-- let input = Stream.fromList [("ONE",1),("ONE",1.1),("TWO",2), ("TWO",2.2)]
2481−-- in Stream.fold (Fold.classify Fold.toList) input
2482−-- :}
2483−-- fromList [("ONE",[1.0,1.1]),("TWO",[2.0,2.2])]
2484−--
2485−-- Same as:
2486−--
2487−-- >>> classify = Fold.classifyWith fst . Fold.lmap snd
2488−--
2489−-- /Pre-release/
2490−{-# INLINE classify #-}
2491−classify :: (Monad m, Ord k) => Fold m a b -> Fold m (k, a) (Map k b)
2492−classify = classifyWith fst . lmap snd
2493−
2494−-- | Determine the frequency of each element in the stream.
2495−--
2496−-- You can just collect the keys of the resulting map to get the unique
2497−-- elements in the stream.
2498−--
2499−{-# INLINE frequency #-}
2500−frequency :: (Monad m, Ord a) => Fold m a (Map a Int)
2501−frequency = classifyWith id length
2502−
25032126------------------------------------------------------------------------------
25042127-- Unzipping
25052128------------------------------------------------------------------------------
@@ -2522,7 +2145,9 @@ unzipWithMUsing t f fld1 fld2 =
25222145
25232146-- | Like 'unzipWith' but with a monadic splitter function.
25242147--
2525−-- @unzipWithM k f1 f2 = lmapM k (unzip f1 f2)@
2148+-- Definition:
2149+--
2150+-- >>> unzipWithM k f1 f2 = Fold.lmapM k (Fold.unzip f1 f2)
25262151--
25272152-- /Pre-release/
25282153{-# INLINE unzipWithM #-}
@@ -2532,7 +2157,6 @@ unzipWithM = unzipWithMUsing teeWith
25322157
25332158-- | Similar to 'unzipWithM' but terminates when the first fold terminates.
25342159--
2535−--
25362160{-# INLINE unzipWithFstM #-}
25372161unzipWithFstM :: Monad m =>
25382162 (a -> m (b, c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)
@@ -2540,7 +2164,6 @@ unzipWithFstM = unzipWithMUsing teeWithFst
25402164
25412165-- | Similar to 'unzipWithM' but terminates when any fold terminates.
25422166--
2543−--
25442167{-# INLINE unzipWithMinM #-}
25452168unzipWithMinM :: Monad m =>
25462169 (a -> m (b,c)) -> Fold m b x -> Fold m c y -> Fold m a (x,y)
@@ -2549,7 +2172,10 @@ unzipWithMinM = unzipWithMUsing teeWithMin
25492172-- | Split elements in the input stream into two parts using a pure splitter
25502173-- function, direct each part to a different fold and zip the results.
25512174--
2552−-- @unzipWith f fld1 fld2 = Fold.lmap f (Fold.unzip fld1 fld2)@
2175+-- Definitions:
2176+--
2177+-- >>> unzipWith f = Fold.unzipWithM (return . f)
2178+-- >>> unzipWith f fld1 fld2 = Fold.lmap f (Fold.unzip fld1 fld2)
25532179--
25542180-- This fold terminates when both the input folds terminate.
25552181--
@@ -2570,7 +2196,9 @@ unzipWith f = unzipWithM (return . f)
25702196--
25712197-- @
25722198--
2573−-- > unzip = Fold.unzipWith id
2199+-- Definition:
2200+--
2201+-- >>> unzip = Fold.unzipWith id
25742202--
25752203-- This is the consumer side dual of the producer side 'zip' operation.
25762204--
@@ -2588,22 +2216,27 @@ unzip = unzipWith id
25882216-- handled in parsers using the Continue facility. See zipWithM in the Parser
25892217-- module.
25902218--
2219+-- cmpBy, eqBy, isPrefixOf, isSubsequenceOf etc can be implemented using
2220+-- zipStream.
2221+
25912222-- | Zip a stream with the input of a fold using the supplied function.
25922223--
25932224-- /Unimplemented/
25942225--
2595−{-# INLINE zipWithM #-}
2596−zipWithM :: -- Monad m =>
2597− (a -> b -> m c) -> t m a -> Fold m c x -> Fold m b x
2598−zipWithM = undefined
2226+{-# INLINE zipStreamWithM #-}
2227+zipStreamWithM :: -- Monad m =>
2228+ (a -> b -> m c) -> Stream m a -> Fold m c x -> Fold m b x
2229+zipStreamWithM = undefined
25992230
26002231-- | Zip a stream with the input of a fold.
26012232--
2233+-- >>> zip = Fold.zipStreamWithM (curry return)
2234+--
26022235-- /Unimplemented/
26032236--
2604−{-# INLINE zip #-}
2605−zip :: Monad m => t m a -> Fold m (a, b) x -> Fold m b x
2606−zip = zipWithM (curry return)
2237+{-# INLINE zipStream #-}
2238+zipStream :: Monad m => Stream m a -> Fold m (a, b) x -> Fold m b x
2239+zipStream = zipStreamWithM (curry return)
26072240
26082241-- | Pair each element of a fold input with its index, starting from index 0.
26092242--
@@ -2618,16 +2251,24 @@ indexingWith i f = fmap toMaybe $ foldl' step initial
26182251 step Nothing' a = Just' (i, a)
26192252 step (Just' (n, _)) a = Just' (f n, a)
26202253
2254+-- |
2255+-- >>> indexing = Fold.indexingWith 0 (+ 1)
2256+--
26212257{-# INLINE indexing #-}
26222258indexing :: Monad m => Fold m a (Maybe (Int, a))
26232259indexing = indexingWith 0 (+ 1)
26242260
2261+-- |
2262+-- >>> indexingRev n = Fold.indexingWith n (subtract 1)
2263+--
26252264{-# INLINE indexingRev #-}
26262265indexingRev :: Monad m => Int -> Fold m a (Maybe (Int, a))
26272266indexingRev n = indexingWith n (subtract 1)
26282267
26292268-- | Pair each element of a fold input with its index, starting from index 0.
26302269--
2270+-- >>> indexed = Fold.scanMaybe Fold.indexing
2271+--
26312272{-# INLINE indexed #-}
26322273indexed :: Monad m => Fold m (Int, a) b -> Fold m a b
26332274indexed = scanMaybe indexing
@@ -2636,8 +2277,9 @@ indexed = scanMaybe indexing
26362277-- additional state input @(s, a) -> b@. Convenient to filter with an
26372278-- addiitonal index or time input.
26382279--
2280+-- >>> filterWithIndex = Fold.with Fold.indexed Fold.filter
2281+--
26392282-- @
2640−-- filterWithIndex = with indexed filter
26412283-- filterWithAbsTime = with timestamped filter
26422284-- filterWithRelTime = with timeIndexed filter
26432285-- @
@@ -2756,12 +2398,14 @@ unfoldMany (Unfold ustep inject) (Fold fstep initial extract) =
27562398 {-# INLINE_LATE consume #-}
27572399 consume s a = inject a >>= produce s
27582400
2759−{-# INLINE topBy #-}
2760−topBy :: (MonadIO m, Unbox a) =>
2401+-- | Get the bottom most @n@ elements using the supplied comparison function.
2402+--
2403+{-# INLINE bottomBy #-}
2404+bottomBy :: (MonadIO m, Unbox a) =>
27612405 (a -> a -> Ordering)
27622406 -> Int
27632407 -> Fold m a (MA.Array a)
2764−topBy cmp n = Fold step initial extract
2408+bottomBy cmp n = Fold step initial extract
27652409
27662410 where
27672411
@@ -2790,6 +2434,10 @@ topBy cmp n = Fold step initial extract
27902434
27912435-- | Fold the input stream to top n elements.
27922436--
2437+-- Definition:
2438+--
2439+-- >>> top = Fold.bottomBy $ flip compare
2440+--
27932441-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]
27942442-- >>> Stream.fold (Fold.top 3) stream >>= MA.toList
27952443-- [17,11,9]
@@ -2797,10 +2445,14 @@ topBy cmp n = Fold step initial extract
27972445-- /Pre-release/
27982446{-# INLINE top #-}
27992447top :: (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (MA.Array a)
2800−top = topBy $ flip compare
2448+top = bottomBy $ flip compare
28012449
28022450-- | Fold the input stream to bottom n elements.
28032451--
2452+-- Definition:
2453+--
2454+-- >>> bottom = Fold.bottomBy compare
2455+--
28042456-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]
28052457-- >>> Stream.fold (Fold.bottom 3) stream >>= MA.toList
28062458-- [1,2,3]
@@ -2808,4 +2460,4 @@ top = topBy $ flip compare
28082460-- /Pre-release/
28092461{-# INLINE bottom #-}
28102462bottom :: (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (MA.Array a)
2811−bottom = topBy compare
2463+bottom = bottomBy compare
core/src/Streamly/Internal/Data/Fold/Type.hs+223−94
@@ -322,18 +322,11 @@
322322-- can be expressed using 'mconcat' and a suitable 'Monoid'. Instead of
323323-- writing folds we can write Monoids and turn them into folds.
324324--
325−-- = Performance Notes
326−--
327−-- 'Streamly.Prelude' module provides fold functions to directly fold streams
328−-- e.g. Streamly.Prelude/'Streamly.Prelude.sum' serves the same purpose as
329−-- Fold/'sum'. However, the functions in Streamly.Prelude cannot be
330−-- efficiently combined together e.g. we cannot drive the input stream through
331−-- @sum@ and @length@ fold functions simultaneously. Using the 'Fold' type we
332−-- can efficiently split the stream across multiple folds because it allows the
333−-- compiler to perform stream fusion optimizations.
334−--
335325module Streamly.Internal.Data.Fold.Type
336326 (
327+ -- * Imports
328+ -- $setup
329+
337330 -- * Types
338331 Step (..)
339332 , Fold (..)
@@ -344,8 +337,8 @@ module Streamly.Internal.Data.Fold.Type
344337 , foldl1'
345338 , foldt'
346339 , foldtM'
347− , foldr
348− , foldrM
340+ , foldr'
341+ , foldrM'
349342
350343 -- * Folds
351344 , fromPure
@@ -395,6 +388,7 @@ module Streamly.Internal.Data.Fold.Type
395388
396389 -- ** Nested Application
397390 , concatMap
391+ , duplicate
398392 , refold
399393
400394 -- ** Parallel Distribution
@@ -407,11 +401,17 @@ module Streamly.Internal.Data.Fold.Type
407401 , longest
408402
409403 -- * Running A Fold
410− , initialize
404+ , extractM
405+ , reduce
411406 , snoc
412− , duplicate
413− , finish
414− , isDone
407+ , snocM
408+ , snocl
409+ , snoclM
410+ , close
411+ , isClosed
412+
413+ -- * Deprecated
414+ , foldr
415415 )
416416where
417417
@@ -434,6 +434,7 @@ import Prelude hiding (concatMap, filter, foldr, map, take)
434434-- >>> :m
435435-- >>> :set -XFlexibleContexts
436436-- >>> import Data.Maybe (fromJust, isJust)
437+-- >>> import Data.Monoid (Endo(..))
437438-- >>> import Streamly.Data.Fold (Fold)
438439-- >>> import Streamly.Internal.Data.Stream.Type (Stream)
439440-- >>> import qualified Data.Foldable as Foldable
@@ -504,8 +505,6 @@ rmapM f (Fold step initial extract) = Fold step1 initial1 (extract >=> f)
504505-- mkfoldlx step initial extract = fmap extract (foldl' step initial)
505506-- @
506507--
507−-- See also: @Streamly.Prelude.foldl'@
508−--
509508{-# INLINE foldl' #-}
510509foldl' :: Monad m => (b -> a -> b) -> b -> Fold m a b
511510foldl' step initial =
@@ -524,8 +523,6 @@ foldl' step initial =
524523-- mkFoldlxM step initial extract = rmapM extract (foldlM' step initial)
525524-- @
526525--
527−-- See also: @Streamly.Prelude.foldlM'@
528−--
529526{-# INLINE foldlM' #-}
530527foldlM' :: Monad m => (b -> a -> m b) -> m b -> Fold m a b
531528foldlM' step initial =
@@ -534,8 +531,6 @@ foldlM' step initial =
534531-- | Make a strict left fold, for non-empty streams, using first element as the
535532-- starting value. Returns Nothing if the stream is empty.
536533--
537−-- See also: @Streamly.Prelude.foldl1'@
538−--
539534-- /Pre-release/
540535{-# INLINE foldl1' #-}
541536foldl1' :: Monad m => (a -> a -> a) -> Fold m a (Maybe a)
@@ -552,34 +547,43 @@ foldl1' step = fmap toMaybe $ foldl' step1 Nothing'
552547
553548-- | Make a fold using a right fold style step function and a terminal value.
554549-- It performs a strict right fold via a left fold using function composition.
555−-- Note that this is strict fold, it can only be useful for constructing strict
550+-- Note that a strict right fold can only be useful for constructing strict
556551-- structures in memory. For reductions this will be very inefficient.
557552--
558−-- For example,
553+-- Definitions:
559554--
560−-- > toList = foldr (:) []
555+-- >>> foldr' f z = fmap (flip appEndo z) $ Fold.foldMap (Endo . f)
556+-- >>> foldr' f z = fmap ($ z) $ Fold.foldl' (\g x -> g . f x) id
561557--
562−-- See also: 'Streamly.Prelude.foldr'
558+-- Example:
563559--
560+-- >>> Stream.fold (Fold.foldr' (:) []) $ Stream.enumerateFromTo 1 5
561+-- [1,2,3,4,5]
562+--
563+{-# INLINE foldr' #-}
564+foldr' :: Monad m => (a -> b -> b) -> b -> Fold m a b
565+foldr' f z = fmap ($ z) $ foldl' (\g x -> g . f x) id
566+
567+{-# DEPRECATED foldr "Please use foldr' instead." #-}
564568{-# INLINE foldr #-}
565569foldr :: Monad m => (a -> b -> b) -> b -> Fold m a b
566−foldr g z = fmap ($ z) $ foldl' (\f x -> f . g x) id
570+foldr = foldr'
567571
568572-- XXX we have not seen any use of this yet, not releasing until we have a use
569573-- case.
574+
575+-- | Like foldr' but with a monadic step function.
570576--
571−-- | Like 'foldr' but with a monadic step function.
572−--
573−-- For example,
577+-- Example:
574578--
575−-- > toList = foldrM (\a xs -> return $ a : xs) (return [])
579+-- >>> toList = Fold.foldrM' (\a xs -> return $ a : xs) (return [])
576580--
577−-- See also: 'Streamly.Prelude.foldrM'
581+-- See also: 'Streamly.Internal.Data.Stream.foldrM'
578582--
579583-- /Pre-release/
580−{-# INLINE foldrM #-}
581−foldrM :: Monad m => (a -> b -> m b) -> m b -> Fold m a b
582−foldrM g z =
584+{-# INLINE foldrM' #-}
585+foldrM' :: Monad m => (a -> b -> m b) -> m b -> Fold m a b
586+foldrM' g z =
583587 rmapM (z >>=) $ foldlM' (\f x -> return $ g x >=> f) (return return)
584588
585589------------------------------------------------------------------------------
@@ -644,7 +648,8 @@ fromRefold (Refold step inject extract) c =
644648-- | A fold that drains all its input, running the effects and discarding the
645649-- results.
646650--
647−-- > drain = drainBy (const (return ()))
651+-- >>> drain = Fold.drainMapM (const (return ()))
652+-- >>> drain = Fold.foldl' (\_ _ -> ()) ()
648653--
649654{-# INLINE drain #-}
650655drain :: Monad m => Fold m a ()
@@ -656,11 +661,11 @@ drain = foldl' (\_ _ -> ()) ()
656661-- very inefficient, consider using "Streamly.Data.Array.Unboxed"
657662-- instead.
658663--
659−-- > toList = foldr (:) []
664+-- >>> toList = Fold.foldr' (:) []
660665--
661666{-# INLINE toList #-}
662667toList :: Monad m => Fold m a [a]
663−toList = foldr (:) []
668+toList = foldr' (:) []
664669
665670-- | Buffers the input stream to a pure stream in the reverse order of the
666671-- input.
@@ -702,9 +707,28 @@ instance Functor m => Functor (Fold m a) where
702707 step s b = fmap2 f (step1 s b)
703708 fmap2 g = fmap (fmap g)
704709
705−-- This is the dual of stream "fromPure".
706−--
707−-- | A fold that always yields a pure value without consuming any input.
710+-- XXX These are singleton folds that are closed for input. The correspondence
711+-- to a nil stream would be a nil fold that returns "Done" in "initial" i.e. it
712+-- does not produce any accumulator value. However, we do not have a
713+-- representation of an empty value in folds, because the Done constructor
714+-- always produces a value (Done b). We can potentially use "Partial s b" and
715+-- "Done" to make the type correspond to the stream type. That may be possible
716+-- if we introduce the "Skip" constructor as well because after the last
717+-- "Partial s b" we have to emit a "Skip to Done" state to keep cranking the
718+-- fold until it is done.
719+--
720+-- There is also the asymmetry between folds and streams because folds have an
721+-- "initial" to initialize the fold without any input. A similar concept is
722+-- possible in streams as well to stop the stream. That would be a "closing"
723+-- operation for the stream which can be called even without consuming any item
724+-- from the stream or when we are done consuming.
725+--
726+-- However, the initial action in folds creates a discrepancy with the CPS
727+-- folds, and the same may be the case if we have a stop/cleanup operation in
728+-- streams.
729+
730+-- | Make a fold that yields the supplied value without consuming any further
731+-- input.
708732--
709733-- /Pre-release/
710734--
@@ -712,10 +736,8 @@ instance Functor m => Functor (Fold m a) where
712736fromPure :: Applicative m => b -> Fold m a b
713737fromPure b = Fold undefined (pure $ Done b) pure
714738
715−-- This is the dual of stream "fromEffect".
716−--
717−-- | A fold that always yields the result of an effectful action without
718−-- consuming any input.
739+-- | Make a fold that yields the result of the supplied effectful action
740+-- without consuming any further input.
719741--
720742-- /Pre-release/
721743--
@@ -732,17 +754,22 @@ data SeqFoldState sl f sr = SeqFoldL !sl | SeqFoldR !f !sr
732754-- or if the input stream is over, the outputs of the two folds are combined
733755-- using the supplied function.
734756--
735−-- >>> f = Fold.serialWith (,) (Fold.take 8 Fold.toList) (Fold.takeEndBy (== '\n') Fold.toList)
757+-- Example:
758+--
759+-- >>> header = Fold.take 8 Fold.toList
760+-- >>> line = Fold.takeEndBy (== '\n') Fold.toList
761+-- >>> f = Fold.serialWith (,) header line
736762-- >>> Stream.fold f $ Stream.fromList "header: hello\n"
737763-- ("header: ","hello\n")
738764--
739−-- Note: This is dual to appending streams using 'Streamly.Prelude.serial'.
765+-- Note: This is dual to appending streams using 'Data.Stream.append'.
740766--
741767-- Note: this implementation allows for stream fusion but has quadratic time
742768-- complexity, because each composition adds a new branch that each subsequent
743769-- fold's input element has to traverse, therefore, it cannot scale to a large
744770-- number of compositions. After around 100 compositions the performance starts
745−-- dipping rapidly compared to a CPS style implementation.
771+-- dipping rapidly compared to a CPS style implementation. When you need
772+-- scaling use parser monad instead.
746773--
747774-- /Time: O(n^2) where n is the number of compositions./
748775--
@@ -828,16 +855,20 @@ data TeeState sL sR bL bR
828855-- | @teeWith k f1 f2@ distributes its input to both @f1@ and @f2@ until both
829856-- of them terminate and combines their output using @k@.
830857--
858+-- Definition:
859+--
860+-- >>> teeWith k f1 f2 = fmap (uncurry k) (Fold.tee f1 f2)
861+--
862+-- Example:
863+--
831864-- >>> avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)
832865-- >>> Stream.fold avg $ Stream.fromList [1.0..100.0]
833866-- 50.5
834867--
835−-- > teeWith k f1 f2 = fmap (uncurry k) ((Fold.tee f1 f2)
836−--
837868-- For applicative composition using this combinator see
838−-- "Streamly.Internal.Data.Fold.Tee".
869+-- "Streamly.Data.Fold.Tee".
839870--
840−-- See also: "Streamly.Internal.Data.Fold.Tee"
871+-- See also: "Streamly.Data.Fold.Tee"
841872--
842873{-# INLINE teeWith #-}
843874teeWith :: Monad m => (a -> b -> c) -> Fold m x a -> Fold m x b -> Fold m x c
@@ -1092,10 +1123,15 @@ concatMap f (Fold stepa initiala extracta) = Fold stepc initialc extractc
10921123
10931124-- | @lmap f fold@ maps the function @f@ on the input of the fold.
10941125--
1095−-- >>> Stream.fold (Fold.lmap (\x -> x * x) Fold.sum) (Stream.enumerateFromTo 1 100)
1096−-- 338350
1126+-- Definition:
1127+--
1128+-- >>> lmap = Fold.lmapM return
10971129--
1098−-- > lmap = Fold.lmapM return
1130+-- Example:
1131+--
1132+-- >>> sumSquared = Fold.lmap (\x -> x * x) Fold.sum
1133+-- >>> Stream.fold sumSquared (Stream.enumerateFromTo 1 100)
1134+-- 338350
10991135--
11001136{-# INLINE lmap #-}
11011137lmap :: (a -> b) -> Fold m b r -> Fold m a r
@@ -1247,6 +1283,10 @@ rights = filter isRight . lmap (fromRight undefined)
12471283-- | Remove the either wrapper and flatten both lefts and as well as rights in
12481284-- the output stream.
12491285--
1286+-- Definition:
1287+--
1288+-- >>> both = Fold.lmap (either id id)
1289+--
12501290-- /Pre-release/
12511291--
12521292{-# INLINE both #-}
@@ -1328,29 +1368,18 @@ take n (Fold fstep finitial fextract) = Fold step initial extract
13281368-- Nesting
13291369------------------------------------------------------------------------------
13301370
1371+-- Similar to the comonad "duplicate" operation.
1372+
13311373-- | 'duplicate' provides the ability to run a fold in parts. The duplicated
13321374-- fold consumes the input and returns the same fold as output instead of
13331375-- returning the final result, the returned fold can be run later to consume
13341376-- more input.
13351377--
1336−-- We can append a stream to a fold as follows:
1337−--
1338−-- >>> :{
1339−-- foldAppend :: Monad m => Fold m a b -> Stream m a -> m (Fold m a b)
1340−-- foldAppend f = Stream.fold (Fold.duplicate f)
1341−-- :}
1342−--
1343−-- >>> :{
1344−-- do
1345−-- sum1 <- foldAppend Fold.sum (Stream.enumerateFromTo 1 10)
1346−-- sum2 <- foldAppend sum1 (Stream.enumerateFromTo 11 20)
1347−-- Stream.fold sum2 (Stream.enumerateFromTo 21 30)
1348−-- :}
1349−-- 465
1350−--
13511378-- 'duplicate' essentially appends a stream to the fold without finishing the
13521379-- fold. Compare with 'snoc' which appends a singleton value to the fold.
13531380--
1381+-- See also 'Streamly.Internal.Data.Stream.build'.
1382+--
13541383-- /Pre-release/
13551384{-# INLINE duplicate #-}
13561385duplicate :: Monad m => Fold m a b -> Fold m a (Fold m a b)
@@ -1363,23 +1392,94 @@ duplicate (Fold step1 initial1 extract1) =
13631392
13641393 step s a = second fromPure <$> step1 s a
13651394
1366−-- | Run the initialization effect of a fold. The returned fold would use the
1367−-- value returned by this effect as its initial value.
1395+-- If there were a finalize/flushing action in the stream type that would be
1396+-- equivalent to running initialize in Fold. But we do not have a flushing
1397+-- action in streams.
1398+
1399+-- | Evaluate the initialization effect of a fold. If we are building the fold
1400+-- by chaining lazy actions in fold init this would reduce the actions to a
1401+-- strict accumulator value.
13681402--
13691403-- /Pre-release/
1370−{-# INLINE initialize #-}
1371−initialize :: Monad m => Fold m a b -> m (Fold m a b)
1372−initialize (Fold step initial extract) = do
1404+{-# INLINE reduce #-}
1405+reduce :: Monad m => Fold m a b -> m (Fold m a b)
1406+reduce (Fold step initial extract) = do
13731407 i <- initial
13741408 return $ Fold step (return i) extract
13751409
1376−-- | Append a singleton value to the fold.
1410+-- This is the dual of Stream @cons@.
1411+
1412+-- | Append an effect to the fold lazily, in other words run a single
1413+-- step of the fold.
1414+--
1415+-- /Pre-release/
1416+{-# INLINE snoclM #-}
1417+snoclM :: Monad m => Fold m a b -> m a -> Fold m a b
1418+snoclM (Fold fstep finitial fextract) action = Fold fstep initial fextract
1419+
1420+ where
1421+
1422+ initial = do
1423+ res <- finitial
1424+ case res of
1425+ Partial fs -> action >>= fstep fs
1426+ Done b -> return $ Done b
1427+
1428+-- | Append a singleton value to the fold lazily, in other words run a single
1429+-- step of the fold.
1430+--
1431+-- Definition:
1432+--
1433+-- >>> snocl f = Fold.snoclM f . return
1434+--
1435+-- Example:
13771436--
13781437-- >>> import qualified Data.Foldable as Foldable
1379−-- >>> Foldable.foldlM Fold.snoc Fold.toList [1..3] >>= Fold.finish
1438+-- >>> Fold.extractM $ Foldable.foldl Fold.snocl Fold.toList [1..3]
13801439-- [1,2,3]
13811440--
1382−-- Compare with 'duplicate' which allows appending a stream to the fold.
1441+-- /Pre-release/
1442+{-# INLINE snocl #-}
1443+snocl :: Monad m => Fold m a b -> a -> Fold m a b
1444+-- snocl f = snoclM f . return
1445+snocl (Fold fstep finitial fextract) a = Fold fstep initial fextract
1446+
1447+ where
1448+
1449+ initial = do
1450+ res <- finitial
1451+ case res of
1452+ Partial fs -> fstep fs a
1453+ Done b -> return $ Done b
1454+
1455+-- | Append a singleton value to the fold in other words run a single step of
1456+-- the fold.
1457+--
1458+-- Definition:
1459+--
1460+-- >>> snocM f = Fold.reduce . Fold.snoclM f
1461+--
1462+-- /Pre-release/
1463+{-# INLINE snocM #-}
1464+snocM :: Monad m => Fold m a b -> m a -> m (Fold m a b)
1465+snocM (Fold step initial extract) action = do
1466+ res <- initial
1467+ r <- case res of
1468+ Partial fs -> action >>= step fs
1469+ Done _ -> return res
1470+ return $ Fold step (return r) extract
1471+
1472+-- | Append a singleton value to the fold, in other words run a single step of
1473+-- the fold.
1474+--
1475+-- Definitions:
1476+--
1477+-- >>> snoc f = Fold.reduce . Fold.snocl f
1478+-- >>> snoc f = Fold.snocM f . return
1479+--
1480+-- >>> import qualified Data.Foldable as Foldable
1481+-- >>> Foldable.foldlM Fold.snoc Fold.toList [1..3] >>= Fold.extractM
1482+-- [1,2,3]
13831483--
13841484-- /Pre-release/
13851485{-# INLINE snoc #-}
@@ -1391,30 +1491,54 @@ snoc (Fold step initial extract) a = do
13911491 Done _ -> return res
13921492 return $ Fold step (return r) extract
13931493
1394−-- | Finish the fold to extract the current value of the fold.
1494+-- Similar to the comonad "extract" operation.
1495+-- XXX rename to extract. We can use "extr" for the fold extract function.
1496+
1497+-- | Extract the accumulated result of the fold.
1498+--
1499+-- Definition:
1500+--
1501+-- >>> extractM = Fold.drive Stream.nil
13951502--
1396−-- >>> Fold.finish Fold.toList
1503+-- Example:
1504+--
1505+-- >>> Fold.extractM Fold.toList
13971506-- []
13981507--
13991508-- /Pre-release/
1400−{-# INLINE finish #-}
1401−finish :: Monad m => Fold m a b -> m b
1402−finish (Fold _ initial extract) = do
1509+{-# INLINE extractM #-}
1510+extractM :: Monad m => Fold m a b -> m b
1511+extractM (Fold _ initial extract) = do
14031512 res <- initial
14041513 case res of
14051514 Partial fs -> extract fs
14061515 Done b -> return b
14071516
1408−-- | Check if the fold is done and can take no more input.
1517+-- | Close a fold so that it does not accept any more input.
1518+{-# INLINE close #-}
1519+close :: Monad m => Fold m a b -> Fold m a b
1520+close (Fold _ initial1 extract1) = Fold undefined initial undefined
1521+
1522+ where
1523+
1524+ initial = do
1525+ res <- initial1
1526+ case res of
1527+ Partial s -> Done <$> extract1 s
1528+ Done b -> return $ Done b
1529+
1530+-- Corresponds to the null check for streams.
1531+
1532+-- | Check if the fold has terminated and can take no more input.
14091533--
14101534-- /Pre-release/
1411−{-# INLINE isDone #-}
1412−isDone :: Monad m => Fold m a b -> m (Maybe b)
1413−isDone (Fold _ initial _) = do
1535+{-# INLINE isClosed #-}
1536+isClosed :: Monad m => Fold m a b -> m Bool
1537+isClosed (Fold _ initial _) = do
14141538 res <- initial
14151539 return $ case res of
1416− Partial _ -> Nothing
1417− Done b -> Just b
1540+ Partial _ -> False
1541+ Done _ -> True
14181542
14191543------------------------------------------------------------------------------
14201544-- Parsing
@@ -1440,7 +1564,7 @@ data ManyState s1 s2
14401564--
14411565-- Stops when @collect@ stops.
14421566--
1443−-- See also: 'Streamly.Prelude.concatMap', 'Streamly.Prelude.foldMany'
1567+-- See also: 'Data.Stream.concatMap', 'Data.Stream.foldMany'
14441568--
14451569{-# INLINE many #-}
14461570many :: Monad m => Fold m a b -> Fold m b c -> Fold m a c
@@ -1499,7 +1623,7 @@ many (Fold sstep sinitial sextract) (Fold cstep cinitial cextract) =
14991623--
15001624-- /Internal/
15011625--
1502−-- /See also: 'Streamly.Prelude.concatMap', 'Streamly.Prelude.foldMany'/
1626+-- See also: 'Data.Stream.concatMap', 'Data.Stream.foldMany'
15031627--
15041628{-# INLINE manyPost #-}
15051629manyPost :: Monad m => Fold m a b -> Fold m b c -> Fold m a c
@@ -1544,12 +1668,16 @@ manyPost (Fold sstep sinitial sextract) (Fold cstep cinitial cextract) =
15441668-- of @n@ items in the input stream and supplies the result to the @collect@
15451669-- fold.
15461670--
1671+-- Definition:
1672+--
1673+-- >>> chunksOf n split = Fold.many (Fold.take n split)
1674+--
1675+-- Example:
1676+--
15471677-- >>> twos = Fold.chunksOf 2 Fold.toList Fold.toList
15481678-- >>> Stream.fold twos $ Stream.fromList [1..10]
15491679-- [[1,2],[3,4],[5,6],[7,8],[9,10]]
15501680--
1551−-- > chunksOf n split = many (take n split)
1552−--
15531681-- Stops when @collect@ stops.
15541682--
15551683{-# INLINE chunksOf #-}
@@ -1665,4 +1793,5 @@ refoldMany1 (Refold sstep sinject sextract) (Fold cstep cinitial cextract) =
16651793-- /Internal/
16661794{-# INLINE refold #-}
16671795refold :: Monad m => Refold m b a c -> Fold m a b -> Fold m a c
1668−refold (Refold step inject extract) f = Fold step (finish f >>= inject) extract
1796+refold (Refold step inject extract) f =
1797+ Fold step (extractM f >>= inject) extract
core/src/Streamly/Internal/Data/Fold/Window.hs−37
@@ -54,9 +54,6 @@ module Streamly.Internal.Data.Fold.Window
5454 , maximum
5555 , range
5656 , mean
57−
58− -- ** Distribution
59− , frequency
6057 )
6158where
6259
@@ -68,7 +65,6 @@ import Streamly.Internal.Data.Fold.Type (Fold(..), Step(..))
6865import Streamly.Internal.Data.Tuple.Strict
6966 (Tuple'(..), Tuple3Fused' (Tuple3Fused'))
7067
71−import qualified Data.Map as Map
7268import qualified Streamly.Internal.Data.Fold.Type as Fold
7369import qualified Streamly.Internal.Data.Ring.Unboxed as Ring
7470
@@ -353,36 +349,3 @@ maximum n = fmap (fmap snd) $ range n
353349{-# INLINE mean #-}
354350mean :: forall m a. (Monad m, Fractional a) => Fold m (a, Maybe a) a
355351mean = Fold.teeWith (/) sum length
356−
357−-------------------------------------------------------------------------------
358−-- Distribution
359−-------------------------------------------------------------------------------
360−
361−-- XXX We can use a Windowed classifyWith operation, that will allow us to
362−-- express windowed frequency, mode, histograms etc idiomatically.
363−
364−-- | Count the frequency of elements in a sliding window.
365−--
366−-- >>> input = Stream.fromList [1,1,3,4,4::Int]
367−-- >>> f = Ring.slidingWindow 4 FoldW.frequency
368−-- >>> Stream.fold f input
369−-- fromList [(1,1),(3,1),(4,2)]
370−--
371−-- /Pre-release/
372−--
373−{-# INLINE frequency #-}
374−frequency :: (Monad m, Ord a) => Fold m (a, Maybe a) (Map.Map a Int)
375−frequency = Fold.foldl' step Map.empty
376−
377− where
378−
379− decrement v =
380− if v == 1
381− then Nothing
382− else Just (v - 1)
383−
384− step refCountMap (new, mOld) =
385− let m1 = Map.insertWith (+) new 1 refCountMap
386− in case mOld of
387− Just k -> Map.update decrement k m1
388− Nothing -> m1
core/src/Streamly/Internal/Data/Parser.hs+52−11
@@ -105,6 +105,7 @@ module Streamly.Internal.Data.Parser
105105 , listEq
106106 , listEqBy
107107 , eqBy
108+ , subsequenceBy
108109
109110 -- ** By predicate
110111 , takeWhileP
@@ -442,6 +443,8 @@ satisfy = D.toParserK . D.satisfy
442443one :: Monad m => Parser m a a
443444one = satisfy $ const True
444445
446+-- Alternate names: "only", "onlyThis".
447+
445448-- | Match a specific element.
446449--
447450-- >>> oneEq x = Parser.satisfy (== x)
@@ -450,6 +453,8 @@ one = satisfy $ const True
450453oneEq :: (Monad m, Eq a) => a -> Parser m a a
451454oneEq x = satisfy (== x)
452455
456+-- Alternate names: "exclude", "notThis".
457+
453458-- | Match anything other than the supplied element.
454459--
455460-- >>> oneNotEq x = Parser.satisfy (/= x)
@@ -995,27 +1000,40 @@ groupByRollingEither :: Monad m =>
9951000 (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser m a (Either b c)
9961001groupByRollingEither eq f1 = D.toParserK . D.groupByRollingEither eq f1
9971002
1003+-- XXX eqBy is not a good name because we are not matching the entire stream
1004+-- unlike the eqBy in Stream module. matchStreamBy, matchStream, matchListBy,
1005+-- matchList.
1006+
1007+-- | Like 'listEqBy' but uses a stream instead of a list and does not return
1008+-- the stream.
1009+--
1010+-- See also: "Streamly.Data.Stream.eqBy"
1011+--
1012+{-# INLINE eqBy #-}
1013+eqBy :: Monad m => (a -> a -> Bool) -> Stream m a -> Parser m a ()
1014+eqBy cmp = D.toParserK . D.eqBy cmp . Stream.toStreamD
1015+
9981016-- | Match the given sequence of elements using the given comparison function.
9991017-- Returns the original sequence if successful.
10001018--
1019+-- Definition:
1020+--
1021+-- >>> listEqBy cmp xs = Parser.eqBy cmp (Stream.fromList xs) *> Parser.fromPure xs
1022+--
1023+-- Examples:
1024+--
10011025-- >>> Stream.parse (Parser.listEqBy (==) "string") $ Stream.fromList "string"
10021026-- "string"
10031027--
10041028-- >>> Stream.parse (Parser.listEqBy (==) "mismatch") $ Stream.fromList "match"
1005−-- *** Exception: ParseError "listEqBy: failed, yet to match 7 elements"
1029+-- *** Exception: ParseError "eqBy: mismtach occurred"
10061030--
10071031{-# INLINE listEqBy #-}
10081032listEqBy :: Monad m => (a -> a -> Bool) -> [a] -> Parser m a [a]
1009−listEqBy cmp xs = D.toParserK (D.listEqBy cmp xs)
1010−
1011−-- | Like 'listEqBy' but uses a stream instead of a list and does not return
1012−-- the stream.
1013−--
1014−-- Note: A @Stream m a@ isn't returned as it isn't buffered.
1015−{-# INLINE eqBy #-}
1016−eqBy :: Monad m => (a -> a -> Bool) -> Stream m a -> Parser m a ()
1017−eqBy cmp = D.toParserK . D.eqBy cmp . Stream.toStreamD
1033+-- listEqBy cmp xs = D.toParserK (D.listEqBy cmp xs)
1034+listEqBy cmp xs = eqBy cmp (Stream.fromList xs) *> fromPure xs
10181035
1036+-- Rename to "list".
10191037-- | Match the input sequence with the supplied list and return it if
10201038-- successful.
10211039--
@@ -1025,6 +1043,29 @@ eqBy cmp = D.toParserK . D.eqBy cmp . Stream.toStreamD
10251043listEq :: (Monad m, Eq a) => [a] -> Parser m a [a]
10261044listEq = listEqBy (==)
10271045
1046+-- | Match if the input stream is a subsequence of the argument stream i.e. all
1047+-- the elements of the input stream occur, in order, in the argument stream.
1048+-- The elements do not have to occur consecutively. A sequence is considered a
1049+-- subsequence of itself.
1050+{-# INLINE subsequenceBy #-}
1051+subsequenceBy :: -- Monad m =>
1052+ (a -> a -> Bool) -> Stream m a -> Parser m a ()
1053+subsequenceBy = undefined
1054+
1055+{-
1056+-- Should go in Data.Parser.Regex in streamly package so that it can depend on
1057+-- regex backends.
1058+{-# INLINE regexPosix #-}
1059+regexPosix :: -- Monad m =>
1060+ Regex -> Parser m a (Maybe (Array (MatchOffset, MatchLength)))
1061+regexPosix = undefined
1062+
1063+{-# INLINE regexPCRE #-}
1064+regexPCRE :: -- Monad m =>
1065+ Regex -> Parser m a (Maybe (Array (MatchOffset, MatchLength)))
1066+regexPCRE = undefined
1067+-}
1068+
10281069-------------------------------------------------------------------------------
10291070-- nested parsers
10301071-------------------------------------------------------------------------------
@@ -1432,7 +1473,7 @@ sepBy1 :: Monad m =>
14321473 Parser m a b -> Parser m a x -> Fold m b c -> Parser m a c
14331474sepBy1 p sep sink = do
14341475 x <- p
1435− f <- fromEffect $ FL.initialize sink
1476+ f <- fromEffect $ FL.reduce sink
14361477 f1 <- fromEffect $ FL.snoc f x
14371478 many (sep >> p) f1
14381479
core/src/Streamly/Internal/Data/Stream/Bottom.hs+52−40
@@ -21,13 +21,16 @@ module Streamly.Internal.Data.Stream.Bottom
2121
2222 -- * Folds
2323 , fold
24− , foldContinue
2524 , foldBreak
2625 , foldBreak2
2726 , foldEither
2827 , foldEither2
2928 , foldConcat
3029
30+ -- * Builders
31+ , build
32+ , buildl
33+
3134 -- * Scans
3235 , smapM
3336 -- $smapM_Notes
@@ -75,7 +78,6 @@ import Streamly.Internal.Data.SVar.Type (defState)
7578
7679import qualified Streamly.Internal.Data.Array.Unboxed.Type as A
7780import qualified Streamly.Internal.Data.Fold as Fold
78−import qualified Streamly.Internal.Data.Stream.Common as Common
7981import qualified Streamly.Internal.Data.Stream.StreamK as K
8082import qualified Streamly.Internal.Data.Stream.StreamD as D
8183
@@ -86,7 +88,7 @@ import Streamly.Internal.Data.Stream.Type
8688--
8789-- $setup
8890-- >>> :m
89−-- >>> import Control.Monad (join)
91+-- >>> import Control.Monad (join, (>=>), (<=<))
9092-- >>> import Control.Monad.Trans.Class (lift)
9193-- >>> import Data.Function (fix, (&))
9294-- >>> import Data.Maybe (fromJust, isJust)
@@ -99,20 +101,6 @@ import Streamly.Internal.Data.Stream.Type
99101-- >>> import qualified Streamly.Internal.Data.Parser as Parser
100102-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
101103
102−------------------------------------------------------------------------------
103−-- Generation
104−------------------------------------------------------------------------------
105−
106−-- |
107−-- >>> fromList = Prelude.foldr Stream.cons Stream.nil
108−--
109−-- Construct a stream from a list of pure values. This is more efficient than
110−-- 'fromFoldable'.
111−--
112−{-# INLINE fromList #-}
113−fromList :: Monad m => [a] -> Stream m a
114−fromList = fromStreamK . Common.fromList
115−
116104------------------------------------------------------------------------------
117105-- Generation - Time related
118106------------------------------------------------------------------------------
@@ -126,7 +114,7 @@ fromList = fromStreamK . Common.fromList
126114-- terms of CPU usage. Any granularity lower than 1 ms is treated as 1 ms.
127115--
128116-- >>> import Control.Concurrent (threadDelay)
129−-- >>> f = Fold.drainBy (\x -> print x >> threadDelay 1000000)
117+-- >>> f = Fold.drainMapM (\x -> print x >> threadDelay 1000000)
130118-- >>> Stream.fold f $ Stream.take 3 $ Stream.timesWith 0.01
131119-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
132120-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
@@ -145,7 +133,7 @@ timesWith g = fromStreamD $ D.times g
145133-- expensive in terms of CPU usage. Any granularity lower than 1 ms is treated
146134-- as 1 ms.
147135--
148−-- >>> f = Fold.drainBy print
136+-- >>> f = Fold.drainMapM print
149137-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.absTimesWith 0.01
150138-- AbsTime (TimeSpec {sec = ..., nsec = ...})
151139-- AbsTime (TimeSpec {sec = ..., nsec = ...})
@@ -164,7 +152,7 @@ absTimesWith = fmap (uncurry addToAbsTime64) . timesWith
164152-- clock is more expensive in terms of CPU usage. Any granularity lower than 1
165153-- ms is treated as 1 ms.
166154--
167−-- >>> f = Fold.drainBy print
155+-- >>> f = Fold.drainMapM print
168156-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.relTimesWith 0.01
169157-- RelTime64 (NanoSecond64 ...)
170158-- RelTime64 (NanoSecond64 ...)
@@ -182,40 +170,64 @@ relTimesWith = fmap snd . timesWith
182170-- Elimination - Running a Fold
183171------------------------------------------------------------------------------
184172
185−-- | We can create higher order folds using 'foldContinue'. We can fold a
186−-- number of streams to a given fold efficiently with full stream fusion. For
187−-- example, to fold a list of streams on the same sum fold:
173+-- | Append a stream to a fold lazily to build an accumulator incrementally.
188174--
189−-- >>> concatFold = Prelude.foldl Stream.foldContinue Fold.sum
175+-- Example, to continue folding a list of streams on the same sum fold:
190176--
191−-- >>> fold f = Fold.finish . Stream.foldContinue f
177+-- >>> streams = [Stream.fromList [1..5], Stream.fromList [6..10]]
178+-- >>> f = Prelude.foldl Stream.buildl Fold.sum streams
179+-- >>> Fold.extractM f
180+-- 55
192181--
193−{-# INLINE foldContinue #-}
194−foldContinue :: Monad m => Fold m a b -> Stream m a -> Fold m a b
195−foldContinue f s = D.foldContinue f $ toStreamD s
182+{-# INLINE buildl #-}
183+buildl :: Monad m => Fold m a b -> Stream m a -> Fold m a b
184+buildl f s = D.foldContinue f $ toStreamD s
185+
186+-- | Append a stream to a fold strictly to build an accumulator incrementally.
187+--
188+-- Definitions:
189+--
190+-- >>> build f = Stream.fold (Fold.duplicate f)
191+-- >>> build f = Stream.buildl f >=> Fold.reduce
192+--
193+-- Example:
194+--
195+-- >>> :{
196+-- do
197+-- sum1 <- Stream.build Fold.sum (Stream.enumerateFromTo 1 10)
198+-- sum2 <- Stream.build sum1 (Stream.enumerateFromTo 11 20)
199+-- Stream.fold sum2 (Stream.enumerateFromTo 21 30)
200+-- :}
201+-- 465
202+--
203+build :: Monad m => Fold m a b -> Stream m a -> m (Fold m a b)
204+build f = fold (Fold.duplicate f)
196205
197206-- | Fold a stream using the supplied left 'Fold' and reducing the resulting
198207-- expression strictly at each step. The behavior is similar to 'foldl''. A
199208-- 'Fold' can terminate early without consuming the full stream. See the
200209-- documentation of individual 'Fold's for termination behavior.
201210--
202−-- >>> Stream.fold Fold.sum (Stream.enumerateFromTo 1 100)
203−-- 5050
211+-- Definitions:
204212--
205−-- Folds never fail, therefore, they produce a default value even when no input
206−-- is provided. It means we can always fold an empty stream and get a valid
207−-- result. For example:
213+-- >>> fold f = fmap fst . Stream.foldBreak f
214+-- >>> fold f = Fold.extractM . Stream.buildl f
215+-- >>> fold f = Fold.extractM <=< Stream.build f
216+-- >>> fold f = Stream.parse (Parser.fromFold f)
208217--
209−-- >>> Stream.fold Fold.sum Stream.nil
210−-- 0
218+-- Example:
211219--
212−-- >>> fold f = Stream.parse (Parser.fromFold f)
220+-- >>> Stream.fold Fold.sum (Stream.enumerateFromTo 1 100)
221+-- 5050
213222--
214223{-# INLINE fold #-}
215224fold :: Monad m => Fold m a b -> Stream m a -> m b
216225fold fl strm = D.fold fl $ D.fromStreamK $ toStreamK strm
217226
218−-- | Like 'fold' but also returns the remaining stream.
227+-- Alternative name foldSome, but may be confused vs foldMany.
228+
229+-- | Like 'fold' but also returns the remaining stream. The resulting stream
230+-- would be 'Stream.nil' if the stream finished before the fold.
219231--
220232-- /Not fused/
221233--
@@ -246,9 +258,9 @@ foldBreak2 fl strm = fmap f $ D.foldBreak fl $ toStreamD strm
246258
247259 f (b, str) = (b, fromStreamD str)
248260
249−-- | Fold resulting in either breaking the stream or continuation of the fold
261+-- | Fold resulting in either breaking the stream or continuation of the fold.
250262-- Instead of supplying the input stream in one go we can run the fold multiple
251−-- times each time supplying the next segment of the input stream. If the fold
263+-- times, each time supplying the next segment of the input stream. If the fold
252264-- has not yet finished it returns a fold that can be run again otherwise it
253265-- returns the fold result and the residual stream.
254266--
@@ -372,7 +384,7 @@ map f = fromStreamD . D.map f . toStreamD
372384-- Stream.fold Fold.toList
373385-- $ fmap (fromJust . fst)
374386-- $ Stream.takeWhile (\(_,x) -> x <= 10)
375−-- $ Stream.postscan (Fold.tee Fold.last avg) s
387+-- $ Stream.postscan (Fold.tee Fold.latest avg) s
376388-- :}
377389-- [1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0,16.0,17.0,18.0,19.0]
378390--
core/src/Streamly/Internal/Data/Stream/Eliminate.hs+17−5
@@ -24,7 +24,14 @@ module Streamly.Internal.Data.Stream.Eliminate
2424 -- See "Streamly.Internal.Data.Fold".
2525 fold
2626 , foldBreak
27− , foldContinue
27+ , foldBreak2
28+ , foldEither
29+ , foldEither2
30+ , foldConcat
31+
32+ -- * Builders
33+ , build
34+ , buildl
2835
2936 -- * Running a 'Parser'
3037 -- "Streamly.Internal.Data.Parser".
@@ -106,10 +113,15 @@ import Prelude hiding (foldr, init, reverse)
106113-- 'Nothing'. If the stream is non-empty, returns @Just (a, ma)@, where @a@ is
107114-- the head of the stream and @ma@ its tail.
108115--
109−-- This can be used to do pretty much anything in an imperative manner, as it
110−-- just breaks down the stream into individual elements and we can loop over
111−-- them as we deem fit. For example, this can be used to convert a streamly
112−-- stream into other stream types.
116+-- Properties:
117+--
118+-- >>> Nothing <- Stream.uncons Stream.nil
119+-- >>> Just ("a", t) <- Stream.uncons (Stream.cons "a" Stream.nil)
120+--
121+-- This can be used to consume the stream in an imperative manner one element
122+-- at a time, as it just breaks down the stream into individual elements and we
123+-- can loop over them as we deem fit. For example, this can be used to convert
124+-- a streamly stream into other stream types.
113125--
114126-- All the folds in this module can be expressed in terms of 'uncons', however,
115127-- this is generally less efficient than specific folds because it takes apart
core/src/Streamly/Internal/Data/Stream/Exception.hs−45
@@ -19,14 +19,12 @@ module Streamly.Internal.Data.Stream.Exception
1919 , finallyIO
2020 , ghandle
2121 , handle
22− , retry
2322 )
2423where
2524
2625import Control.Exception (Exception)
2726import Control.Monad.Catch (MonadCatch)
2827import Control.Monad.IO.Class (MonadIO)
29−import Data.Map.Strict (Map)
3028import Streamly.Internal.Data.Stream.Type (Stream, fromStreamD, toStreamD)
3129
3230import qualified Streamly.Internal.Data.Stream.StreamD as D
@@ -220,46 +218,3 @@ handle :: (MonadCatch m, Exception e)
220218 => (e -> Stream m a) -> Stream m a -> Stream m a
221219handle handler xs =
222220 fromStreamD $ D.handle (toStreamD . handler) $ toStreamD xs
223−
224−
225−-- | @retry@ takes 3 arguments
226−--
227−-- 1. A map @m@ whose keys are exceptions and values are the number of times to
228−-- retry the action given that the exception occurs.
229−--
230−-- 2. A handler @han@ that decides how to handle an exception when the exception
231−-- cannot be retried.
232−--
233−-- 3. The stream itself that we want to run this mechanism on.
234−--
235−-- When evaluating a stream if an exception occurs,
236−--
237−-- 1. The stream evaluation aborts
238−--
239−-- 2. The exception is looked up in @m@
240−--
241−-- a. If the exception exists and the mapped value is > 0 then,
242−--
243−-- i. The value is decreased by 1.
244−--
245−-- ii. The stream is resumed from where the exception was called, retrying
246−-- the action.
247−--
248−-- b. If the exception exists and the mapped value is == 0 then the stream
249−-- evaluation stops.
250−--
251−-- c. If the exception does not exist then we handle the exception using
252−-- @han@.
253−--
254−-- /Internal/
255−--
256−{-# INLINE retry #-}
257−retry :: (MonadCatch m, Exception e, Ord e)
258− => Map e Int
259− -- ^ map from exception to retry count
260− -> (e -> Stream m a)
261− -- ^ default handler for those exceptions that are not in the map
262− -> Stream m a
263− -> Stream m a
264−retry emap handler inp =
265− fromStreamD $ D.retry emap (toStreamD . handler) $ toStreamD inp
core/src/Streamly/Internal/Data/Stream/Generate.hs+4−4
@@ -225,7 +225,7 @@ replicateM n = Stream.sequence . replicate n
225225-- (epoch) denoting the start of the stream and the second component is a time
226226-- relative to the reference.
227227--
228−-- >>> f = Fold.drainBy (\x -> print x >> threadDelay 1000000)
228+-- >>> f = Fold.drainMapM (\x -> print x >> threadDelay 1000000)
229229-- >>> Stream.fold f $ Stream.take 3 $ Stream.times
230230-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
231231-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
@@ -242,7 +242,7 @@ times = timesWith 0.01
242242-- | @absTimes@ returns a stream of absolute timestamps using a clock of 10 ms
243243-- granularity.
244244--
245−-- >>> f = Fold.drainBy print
245+-- >>> f = Fold.drainMapM print
246246-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.absTimes
247247-- AbsTime (TimeSpec {sec = ..., nsec = ...})
248248-- AbsTime (TimeSpec {sec = ..., nsec = ...})
@@ -259,7 +259,7 @@ absTimes = fmap (uncurry addToAbsTime64) times
259259-- | @relTimes@ returns a stream of relative time values starting from 0,
260260-- using a clock of granularity 10 ms.
261261--
262−-- >>> f = Fold.drainBy print
262+-- >>> f = Fold.drainMapM print
263263-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.relTimes
264264-- RelTime64 (NanoSecond64 ...)
265265-- RelTime64 (NanoSecond64 ...)
@@ -373,7 +373,7 @@ iterateM step = fromStreamD . D.iterateM step
373373-- >>> import System.IO.Unsafe (unsafeInterleaveIO)
374374--
375375-- >>> :{
376−-- main = Stream.fold (Fold.drainBy print) $ Stream.mfix f
376+-- main = Stream.fold (Fold.drainMapM print) $ Stream.mfix f
377377-- where
378378-- f action = do
379379-- let incr n act = fmap ((+n) . snd) $ unsafeInterleaveIO act
core/src/Streamly/Internal/Data/Stream/StreamD/Exception.hs−55
@@ -20,7 +20,6 @@ module Streamly.Internal.Data.Stream.StreamD.Exception
2020 , finally
2121 , ghandle
2222 , handle
23− , retry
2423 )
2524where
2625
@@ -29,13 +28,11 @@ where
2928import Control.Monad.IO.Class (MonadIO(..))
3029import Control.Exception (Exception, SomeException, mask_)
3130import Control.Monad.Catch (MonadCatch)
32−import Data.Map.Strict (Map)
3331import GHC.Exts (inline)
3432import Streamly.Internal.Data.IOFinalizer
3533 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
3634
3735import qualified Control.Monad.Catch as MC
38−import qualified Data.Map.Strict as Map
3936
4037import Streamly.Internal.Data.Stream.StreamD.Type
4138
@@ -369,55 +366,3 @@ _handle f (Stream step state) = Stream step' (Left state)
369366 Yield x s -> return $ Yield x (Right (Stream step1 s))
370367 Skip s -> return $ Skip (Right (Stream step1 s))
371368 Stop -> return Stop
372−
373−data RetryState emap s1 s2
374− = RetryWithMap emap s1
375− | RetryDefault s2
376−
377−-- | See 'Streamly.Internal.Data.Stream.retry'
378−--
379−{-# INLINE_NORMAL retry #-}
380−retry
381− :: forall e m a. (Exception e, Ord e, MonadCatch m)
382− => Map e Int
383− -- ^ map from exception to retry count
384− -> (e -> Stream m a)
385− -- ^ default handler for those exceptions that are not in the map
386− -> Stream m a
387− -> Stream m a
388−retry emap0 defaultHandler (Stream step0 state0) = Stream step state
389−
390− where
391−
392− state = RetryWithMap emap0 state0
393−
394− {-# INLINE_LATE step #-}
395− step gst (RetryWithMap emap st) = do
396− eres <- MC.try $ step0 gst st
397− case eres of
398− Left e -> handler e emap st
399− Right res ->
400− return
401− $ case res of
402− Yield x st1 -> Yield x $ RetryWithMap emap st1
403− Skip st1 -> Skip $ RetryWithMap emap st1
404− Stop -> Stop
405− step gst (RetryDefault (UnStream step1 state1)) = do
406− res <- step1 gst state1
407− return
408− $ case res of
409− Yield x st1 -> Yield x $ RetryDefault (Stream step1 st1)
410− Skip st1 -> Skip $ RetryDefault (Stream step1 st1)
411− Stop -> Stop
412−
413− {-# INLINE handler #-}
414− handler e emap st =
415− return
416− $ Skip
417− $ case Map.lookup e emap of
418− Just i
419− | i > 0 ->
420− let emap1 = Map.insert e (i - 1) emap
421− in RetryWithMap emap1 st
422− | otherwise -> RetryDefault $ defaultHandler e
423− Nothing -> RetryDefault $ defaultHandler e
core/src/Streamly/Internal/Data/Stream/StreamD/Transform.hs−22
@@ -72,7 +72,6 @@ module Streamly.Internal.Data.Stream.StreamD.Transform
7272 , filterM
7373 , deleteBy
7474 , uniq
75− , nub
7675
7776 -- * Trimming
7877 -- | Produce a subset of the stream trimmed at ends.
@@ -133,7 +132,6 @@ import Streamly.Internal.Data.Fold.Type (Fold(..))
133132import Streamly.Internal.Data.Pipe.Type (Pipe(..), PipeState(..))
134133import Streamly.Internal.Data.SVar.Type (defState, adaptState)
135134
136−import qualified Data.Set as Set
137135import qualified Streamly.Internal.Data.Fold.Type as FL
138136import qualified Streamly.Internal.Data.Pipe.Type as Pipe
139137
@@ -703,26 +701,6 @@ uniq (Stream step state) = Stream step' (Nothing, state)
703701 Skip s -> return $ Skip (Just x, s)
704702 Stop -> return Stop
705703
706−-- | The memory used is proportional to the number of unique elements in the
707−-- stream. If we want to limit the memory we can just use "take" to limit the
708−-- uniq elements in the stream.
709−{-# INLINE_NORMAL nub #-}
710−nub :: (Monad m, Ord a) => Stream m a -> Stream m a
711−nub (Stream step1 state1) = Stream step (Set.empty, state1)
712−
713− where
714−
715− step gst (set, st) = do
716− r <- step1 gst st
717− return
718− $ case r of
719− Yield x s ->
720− if Set.member x set
721− then Skip (set, s)
722− else Yield x (Set.insert x set, s)
723− Skip s -> Skip (set, s)
724− Stop -> Stop
725−
726704{-# INLINE_NORMAL deleteBy #-}
727705deleteBy :: Monad m => (a -> a -> Bool) -> a -> Stream m a -> Stream m a
728706deleteBy eq x (Stream step state) = Stream step' (state, False)
core/src/Streamly/Internal/Data/Stream/StreamD/Type.hs−3
@@ -323,9 +323,6 @@ foldBreak fld strm = do
323323
324324 nil = Stream (\_ _ -> return Stop) ()
325325
326−-- | If the fold finishes before the stream, we can detect that the fold is
327−-- done by checking if the initial action returns Done. But the remaining
328−-- stream is discarded.
329326{-# INLINE_NORMAL foldContinue #-}
330327foldContinue :: Monad m => Fold m a b -> Stream m a -> Fold m a b
331328foldContinue (Fold fstep finitial fextract) (Stream sstep state) =
core/src/Streamly/Internal/Data/Stream/Top.hs−115
@@ -32,14 +32,11 @@ module Streamly.Internal.Data.Stream.Top
3232 -- ** Join operations
3333 , crossJoin
3434 , joinInner
35− , joinInnerMap
3635 , joinInnerMerge
3736 , joinLeft
3837 , mergeLeftJoin
39− , joinLeftMap
4038 , joinOuter
4139 , mergeOuterJoin
42− , joinOuterMap
4340 )
4441where
4542
@@ -56,7 +53,6 @@ import Streamly.Internal.Data.Stream.Common ()
5653import Streamly.Internal.Data.Stream.Type (Stream, fromStreamD, toStreamD)
5754
5855import qualified Data.List as List
59−import qualified Data.Map.Strict as Map
6056import qualified Streamly.Internal.Data.Array as Array
6157import qualified Streamly.Internal.Data.Array.Unboxed.Mut.Type as MA
6258import qualified Streamly.Internal.Data.Fold as Fold
@@ -211,44 +207,6 @@ joinInner eq s1 s2 = do
211207 ) s2
212208 ) s1
213209
214−-- XXX Generate error if a duplicate insertion is attempted?
215−toMap :: (Monad m, Ord k) => Stream m (k, v) -> m (Map.Map k v)
216−toMap =
217− let f = Fold.foldl' (\kv (k, b) -> Map.insert k b kv) Map.empty
218− in Stream.fold f
219−
220−-- If the second stream is too big it can be partitioned based on hashes and
221−-- then we can process one parition at a time.
222−--
223−-- XXX An IntMap may be faster when the keys are Int.
224−-- XXX Use hashmap instead of map?
225−--
226−-- | Like 'joinInner' but uses a 'Map' for efficiency.
227−--
228−-- If the input streams have duplicate keys, the behavior is undefined.
229−--
230−-- For space efficiency use the smaller stream as the second stream.
231−--
232−-- Space: O(n)
233−--
234−-- Time: O(m + n)
235−--
236−-- /Pre-release/
237−{-# INLINE joinInnerMap #-}
238−joinInnerMap :: (Monad m, Ord k) =>
239− Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, b)
240−joinInnerMap s1 s2 =
241− Stream.concatM $ do
242− km <- toMap s2
243− pure $ Stream.mapMaybe (joinAB km) s1
244−
245− where
246−
247− joinAB kvm (k, a) =
248− case k `Map.lookup` kvm of
249− Just b -> Just (k, a, b)
250− Nothing -> Nothing
251−
252210-- | Like 'joinInner' but works only on sorted streams.
253211--
254212-- Space: O(1)
@@ -309,28 +267,6 @@ joinLeft eq s1 s2 = Stream.evalStateT (return False) $ do
309267 else Stream.nil
310268 Nothing -> return (a, Nothing)
311269
312−-- | Like 'joinLeft' but uses a hashmap for efficiency.
313−--
314−-- Space: O(n)
315−--
316−-- Time: O(m + n)
317−--
318−-- /Pre-release/
319−{-# INLINE joinLeftMap #-}
320−joinLeftMap :: (Ord k, Monad m) =>
321− Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, Maybe b)
322−joinLeftMap s1 s2 =
323− Stream.concatM $ do
324− km <- toMap s2
325− return $ fmap (joinAB km) s1
326−
327− where
328−
329− joinAB km (k, a) =
330− case k `Map.lookup` km of
331− Just b -> (k, a, Just b)
332− Nothing -> (k, a, Nothing)
333−
334270-- | Like 'joinLeft' but works only on sorted streams.
335271--
336272-- Space: O(1)
@@ -412,57 +348,6 @@ joinOuter eq s1 s =
412348 else Stream.nil
413349 Nothing -> return (Just a, Nothing)
414350
415−-- Put the b's that have been paired, in another hash or mutate the hash to set
416−-- a flag. At the end go through @Stream m b@ and find those that are not in that
417−-- hash to return (Nothing, b).
418−--
419−-- | Like 'joinOuter' but uses a 'Map' for efficiency.
420−--
421−-- Space: O(m + n)
422−--
423−-- Time: O(m + n)
424−--
425−-- /Pre-release/
426−{-# INLINE joinOuterMap #-}
427−joinOuterMap ::
428− (Ord k, MonadIO m) =>
429− Stream m (k, a) -> Stream m (k, b) -> Stream m (k, Maybe a, Maybe b)
430−joinOuterMap s1 s2 =
431− Stream.concatM $ do
432− km1 <- kvFold s1
433− km2 <- kvFold s2
434−
435− -- XXX Not sure if toList/fromList would fuse optimally. We may have to
436− -- create a fused Map.toStream function.
437− let res1 = fmap (joinAB km2)
438− $ Stream.fromList $ Map.toList km1
439− where
440− joinAB km (k, a) =
441− case k `Map.lookup` km of
442− Just b -> (k, Just a, Just b)
443− Nothing -> (k, Just a, Nothing)
444−
445− -- XXX We can take advantage of the lookups in the first pass above to
446− -- reduce the number of lookups in this pass. If we keep mutable cells
447− -- in the second Map, we can flag it in the first pass and not do any
448− -- lookup in the second pass if it is flagged.
449− let res2 = Stream.mapMaybe (joinAB km1)
450− $ Stream.fromList $ Map.toList km2
451− where
452− joinAB km (k, b) =
453− case k `Map.lookup` km of
454− Just _ -> Nothing
455− Nothing -> Just (k, Nothing, Just b)
456−
457− return $ Stream.append res1 res2
458−
459− where
460−
461− -- XXX Generate error if a duplicate insertion is attempted?
462− kvFold =
463− let f = Fold.foldl' (\kv (k, b) -> Map.insert k b kv) Map.empty
464− in Stream.fold f
465−
466351-- | Like 'joinOuter' but works only on sorted streams.
467352--
468353-- Space: O(1)
core/src/Streamly/Internal/Data/Stream/Transform.hs+4−4
@@ -313,7 +313,7 @@ sequence = mapM id
313313-- @
314314--
315315-- >>> s = Stream.enumerateFromTo 1 2
316−-- >>> Stream.fold Fold.drain $ Stream.tap (Fold.drainBy print) s
316+-- >>> Stream.fold Fold.drain $ Stream.tap (Fold.drainMapM print) s
317317-- 1
318318-- 2
319319--
@@ -770,7 +770,7 @@ interspersePrefix_ m = mapM (\x -> void m >> return x)
770770-- | Introduce a delay of specified seconds between elements of the stream.
771771--
772772-- >>> input = Stream.enumerateFromTo 1 3
773−-- >>> Stream.fold (Fold.drainBy print) $ Stream.delay 1 input
773+-- >>> Stream.fold (Fold.drainMapM print) $ Stream.delay 1 input
774774-- 1
775775-- 2
776776-- 3
@@ -786,7 +786,7 @@ delay n = intersperseM_ $ liftIO $ threadDelay $ round $ n * 1000000
786786-- stream.
787787--
788788-- >>> input = Stream.enumerateFromTo 1 3
789−-- >>> Stream.fold (Fold.drainBy print) $ Stream.delayPost 1 input
789+-- >>> Stream.fold (Fold.drainMapM print) $ Stream.delayPost 1 input
790790-- 1
791791-- 2
792792-- 3
@@ -801,7 +801,7 @@ delayPost n = intersperseSuffix_ $ liftIO $ threadDelay $ round $ n * 1000000
801801-- stream.
802802--
803803-- >>> input = Stream.enumerateFromTo 1 3
804−-- >>> Stream.fold (Fold.drainBy print) $ Stream.delayPre 1 input
804+-- >>> Stream.fold (Fold.drainMapM print) $ Stream.delayPre 1 input
805805-- 1
806806-- 2
807807-- 3
core/src/Streamly/Internal/Data/Stream/Type.hs+19−3
@@ -18,6 +18,7 @@ module Streamly.Internal.Data.Stream.Type
1818 , toStreamK
1919 , fromStreamD
2020 , toStreamD
21+ , Streamly.Internal.Data.Stream.Type.fromList
2122
2223 -- * Construction
2324 , cons
@@ -122,6 +123,20 @@ fromStreamD = fromStreamK . D.toStreamK
122123toStreamD :: Applicative m => Stream m a -> D.Stream m a
123124toStreamD = D.fromStreamK . toStreamK
124125
126+------------------------------------------------------------------------------
127+-- Generation
128+------------------------------------------------------------------------------
129+
130+-- |
131+-- >>> fromList = Prelude.foldr Stream.cons Stream.nil
132+--
133+-- Construct a stream from a list of pure values. This is more efficient than
134+-- 'fromFoldable'.
135+--
136+{-# INLINE fromList #-}
137+fromList :: Monad m => [a] -> Stream m a
138+fromList = fromStreamK . P.fromList
139+
125140------------------------------------------------------------------------------
126141-- Comparison
127142------------------------------------------------------------------------------
@@ -320,7 +335,7 @@ instance Show a => Show (Stream Identity a) where
320335instance Read a => Read (Stream Identity a) where
321336 readPrec = parens $ prec 10 $ do
322337 Ident "fromList" <- lexP
323− fromList <$> readPrec
338+ Streamly.Internal.Data.Stream.Type.fromList <$> readPrec
324339
325340 readListPrec = readListPrecDefault
326341
@@ -450,7 +465,7 @@ infixr 5 `consM`
450465consM :: Monad m => m a -> Stream m a -> Stream m a
451466consM m = fromStreamK . K.consM m . toStreamK
452467
453−-- | A pure empty stream with no result and no side-effect.
468+-- | A stream that terminates without producing any output or side effect.
454469--
455470-- >>> Stream.fold Fold.toList Stream.nil
456471-- []
@@ -459,7 +474,8 @@ consM m = fromStreamK . K.consM m . toStreamK
459474nil :: Stream m a
460475nil = fromStreamK K.nil
461476
462−-- | An empty stream producing the supplied side effect.
477+-- | A stream that terminates without producing any output, but produces a side
478+-- effect.
463479--
464480-- >>> Stream.fold Fold.toList (Stream.nilM (print "nil"))
465481-- "nil"
core/src/Streamly/Internal/FileSystem/File.hs+2−2
@@ -114,7 +114,7 @@ import Streamly.Internal.System.IO (defaultChunkSize)
114114import qualified Streamly.Data.Array.Unboxed as A
115115import qualified Streamly.Data.Unfold as UF
116116import qualified Streamly.Internal.Data.Fold.Type as FL
117− (Step(..), snoc, initialize)
117+ (Step(..), snoc, reduce)
118118import qualified Streamly.Internal.Data.Unfold as UF (bracketIO)
119119import qualified Streamly.Internal.FileSystem.Handle as FH
120120import qualified Streamly.Internal.Data.Array.Unboxed.Stream as AS
@@ -432,7 +432,7 @@ writeChunks path = Fold step initial extract
432432 where
433433 initial = do
434434 h <- liftIO (openFile path WriteMode)
435− fld <- FL.initialize (FH.writeChunks h)
435+ fld <- FL.reduce (FH.writeChunks h)
436436 `MC.onException` liftIO (hClose h)
437437 return $ FL.Partial (fld, h)
438438 step (fld, h) x = do
core/src/Streamly/Internal/FileSystem/Handle.hs+2−2
@@ -405,7 +405,7 @@ putChunk h arr = A.asPtrUnsafe arr $ \ptr ->
405405--
406406{-# INLINE putChunks #-}
407407putChunks :: MonadIO m => Handle -> Stream m (Array a) -> m ()
408−putChunks h = S.fold (FL.drainBy (putChunk h))
408+putChunks h = S.fold (FL.drainMapM (putChunk h))
409409
410410-- XXX AS.compact can be written idiomatically in terms of foldMany, just like
411411-- AS.concat is written in terms of foldMany. Once that is done we can write
@@ -452,7 +452,7 @@ putBytes = putBytesWith defaultChunkSize
452452--
453453{-# INLINE writeChunks #-}
454454writeChunks :: MonadIO m => Handle -> Fold m (Array a) ()
455−writeChunks h = FL.drainBy (putChunk h)
455+writeChunks h = FL.drainMapM (putChunk h)
456456
457457-- | Like writeChunks but uses the experimental 'Refold' API.
458458--
core/streamly-core.cabal+12−3
@@ -239,7 +239,6 @@ library
239239 , Streamly.Internal.Control.ForkIO
240240 , Streamly.Internal.Data.Cont
241241 , Streamly.Internal.System.IO
242− , Streamly.Internal.Data.IsMap
243242
244243 -- streamly-strict-data
245244 , Streamly.Internal.Data.Tuple.Strict
@@ -397,6 +396,11 @@ library
397396 , Streamly.Internal.Data.Stream.StreamDK.Type
398397
399398 build-depends:
399+ -- streamly-base
400+ --
401+ -- These dependencies can be reversed if we want
402+ -- streamly-base to depend only on base.
403+ --
400404 -- Core libraries shipped with ghc, the min and max
401405 -- constraints of these libraries should match with
402406 -- the GHC versions we support. This is to make sure that
@@ -404,18 +408,23 @@ library
404408 -- depending on doctest is a common example) can
405409 -- depend on streamly.
406410 base >= 4.12 && < 4.18
407− , containers >= 0.6.0 && < 0.7
408411 , deepseq >= 1.4.4 && < 1.5
409− , directory >= 1.3.3 && < 1.4
410412 , exceptions >= 0.8.0 && < 0.11
411413 , ghc-prim >= 0.5.3 && < 0.10
412414 , mtl >= 2.2.2 && < 2.4
413415 , transformers >= 0.5.5 && < 0.7
416+
417+ -- streamly-unicode-core
414418 , template-haskell >= 2.14 && < 2.20
415419
420+ -- streamly-filesystem-core
421+ , directory >= 1.3.3 && < 1.4
422+
423+ -- fusion-plugin
416424 , fusion-plugin-types >= 0.1 && < 0.2
417425
418426 -- XXX to be removed
427+ , containers >= 0.6.0 && < 0.7
419428 , heaps >= 0.3 && < 0.5
420429 if !flag(use-unliftio)
421430 build-depends: monad-control >= 1.0 && < 1.1
src/Streamly/Internal/Data/Fold/Extra.hsnew file+623
@@ -0,0 +1,623 @@
1+-- |
2+-- Module : Streamly.Internal.Data.Fold.Extra
3+-- Copyright : (c) 2019 Composewell Technologies
4+-- License : BSD3
5+-- Maintainer : streamly@composewell.com
6+-- Stability : experimental
7+-- Portability : GHC
8+--
9+
10+module Streamly.Internal.Data.Fold.Extra
11+ (
12+ -- * Imports
13+ -- $setup
14+
15+ countDistinct
16+ , countDistinctInt
17+ , frequency
18+ , toMap
19+ , nub
20+ , nubInt
21+
22+ -- ** Demultiplexing
23+ -- | Direct values in the input stream to different folds using an n-ary
24+ -- fold selector. 'demux' is a generalization of 'classify' (and
25+ -- 'partition') where each key of the classifier can use a different fold.
26+ , demux
27+ , demuxWith
28+ , demuxScanWith
29+ , demuxScanMutWith
30+ , demuxMutWith
31+
32+ -- 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)
36+
37+ -- ** Classifying
38+ -- | In an input stream of key value pairs fold values for different keys
39+ -- in individual output buckets using the given fold. 'classify' is a
40+ -- special case of 'demux' where all the branches of the demultiplexer use
41+ -- the same fold.
42+ --
43+ -- Different types of maps can be used with these combinators via the IsMap
44+ -- type class. Hashmap performs better when there are more collisions, trie
45+ -- Map performs better otherwise. Trie has an advantage of sorting the keys
46+ -- at the same time. For example if we want to store a dictionary of words
47+ -- and their meanings then trie Map would be better if we also want to
48+ -- display them in sorted order.
49+
50+ , classify
51+ , classifyWith
52+ , classifyMutWith
53+ , classifyScanWith
54+ -- , classifyWithSel
55+ -- , classifyWithMin
56+ )
57+where
58+
59+#include "inline.hs"
60+#include "ArrayMacros.h"
61+
62+import Control.Monad.IO.Class (MonadIO(..))
63+import Data.IORef (newIORef, readIORef, writeIORef)
64+import Data.Map.Strict (Map)
65+import Streamly.Internal.Data.IsMap (IsMap(..))
66+import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))
67+
68+import qualified Data.IntSet as IntSet
69+import qualified Data.Set as Set
70+import qualified Prelude
71+import qualified Streamly.Internal.Data.IsMap as IsMap
72+
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)
80+import Streamly.Internal.Data.Fold
81+
82+-- $setup
83+-- >>> :m
84+-- >>> import qualified Streamly.Data.Fold as Fold
85+-- >>> import qualified Streamly.Data.Stream as Stream
86+-- >>> import qualified Streamly.Internal.Data.Fold.Extra as Fold
87+
88+-- XXX Name as nubOrd? Or write a nubGeneric
89+
90+-- | Used as a scan. Returns 'Just' for the first occurrence of an element,
91+-- returns 'Nothing' for any other occurrences.
92+--
93+-- Example:
94+--
95+-- >>> stream = Stream.fromList [1::Int,1,2,3,4,4,5,1,5,7]
96+-- >>> Stream.fold Fold.toList $ Stream.scanMaybe Fold.nub stream
97+-- [1,2,3,4,5,7]
98+--
99+-- /Pre-release/
100+{-# INLINE nub #-}
101+nub :: (Monad m, Ord a) => Fold m a (Maybe a)
102+nub = fmap (\(Tuple' _ x) -> x) $ foldl' step initial
103+
104+ where
105+
106+ initial = Tuple' Set.empty Nothing
107+
108+ step (Tuple' set _) x =
109+ if Set.member x set
110+ then Tuple' set Nothing
111+ else Tuple' (Set.insert x set) (Just x)
112+
113+-- | Like 'nub' but specialized to a stream of 'Int', for better performance.
114+--
115+-- Definition:
116+--
117+-- >>> nubInt = Fold.nub
118+--
119+-- /Pre-release/
120+{-# INLINE nubInt #-}
121+nubInt :: Monad m => Fold m Int (Maybe Int)
122+nubInt = fmap (\(Tuple' _ x) -> x) $ foldl' step initial
123+
124+ where
125+
126+ initial = Tuple' IntSet.empty Nothing
127+
128+ step (Tuple' set _) x =
129+ if IntSet.member x set
130+ then Tuple' set Nothing
131+ else Tuple' (IntSet.insert x set) (Just x)
132+
133+-- XXX Try Hash set
134+-- XXX Add a countDistinct window fold
135+-- XXX Add a bloom filter fold
136+
137+-- | Count non-duplicate elements in the stream.
138+--
139+-- Definition:
140+--
141+-- >>> countDistinct = Fold.postscan Fold.nub $ Fold.catMaybes $ Fold.length
142+--
143+-- The memory used is proportional to the number of distinct elements in the
144+-- stream, to guard against using too much memory use it as a scan and
145+-- terminate if the count reaches more than a threshold.
146+--
147+-- /Space/: \(\mathcal{O}(n)\)
148+--
149+-- /Pre-release/
150+--
151+{-# INLINE countDistinct #-}
152+countDistinct :: (Monad m, Ord a) => Fold m a Int
153+countDistinct = postscan nub $ catMaybes length
154+{-
155+countDistinct = fmap (\(Tuple' _ n) -> n) $ foldl' step initial
156+
157+ where
158+
159+ initial = Tuple' Set.empty 0
160+
161+ step (Tuple' set n) x = do
162+ if Set.member x set
163+ then
164+ Tuple' set n
165+ else
166+ let cnt = n + 1
167+ in Tuple' (Set.insert x set) cnt
168+-}
169+
170+-- | Like 'countDistinct' but specialized to a stream of 'Int', for better
171+-- performance.
172+--
173+-- Definition:
174+--
175+-- >>> countDistinctInt = Fold.postscan Fold.nubInt $ Fold.catMaybes $ Fold.length
176+--
177+-- /Pre-release/
178+{-# INLINE countDistinctInt #-}
179+countDistinctInt :: Monad m => Fold m Int Int
180+countDistinctInt = postscan nubInt $ catMaybes length
181+{-
182+countDistinctInt = fmap (\(Tuple' _ n) -> n) $ foldl' step initial
183+
184+ where
185+
186+ initial = Tuple' IntSet.empty 0
187+
188+ step (Tuple' set n) x = do
189+ if IntSet.member x set
190+ then
191+ Tuple' set n
192+ else
193+ let cnt = n + 1
194+ in Tuple' (IntSet.insert x set) cnt
195+ -}
196+
197+------------------------------------------------------------------------------
198+-- demux: in a key value stream fold each key sub-stream with a different fold
199+------------------------------------------------------------------------------
200+
201+-- TODO Demultiplex an input element into a number of typed variants. We want
202+-- to statically restrict the target values within a set of predefined types,
203+-- an enumeration of a GADT.
204+--
205+-- This is the consumer side dual of the producer side 'mux' operation (XXX to
206+-- be implemented).
207+--
208+-- XXX If we use Refold in it, it can perhaps fuse/be more efficient. For
209+-- example we can store just the result rather than storing the whole fold in
210+-- the Map.
211+--
212+-- Note: There are separate functions to determine Key and Fold from the input
213+-- because key is to be determined on each input whereas fold is to be
214+-- determined only once for a key.
215+
216+-- | In a key value stream, fold values corresponding to each key with a key
217+-- specific fold. The fold returns the fold result as the second component of
218+-- the output tuple whenever a fold terminates. The first component of the
219+-- tuple is a container of in-progress folds. If a fold terminates, another
220+-- instance of the fold is started upon receiving an input with that key.
221+--
222+-- This can be used to scan a stream and collect the results from the scan
223+-- output.
224+--
225+-- /Pre-release/
226+--
227+{-# INLINE demuxScanWith #-}
228+demuxScanWith :: (Monad m, IsMap f, Traversable f) =>
229+ (a -> Key f)
230+ -> (a -> m (Fold m a b))
231+ -> Fold m a (m (f b), Maybe (Key f, b))
232+demuxScanWith getKey getFold = fmap extract $ foldlM' step initial
233+
234+ where
235+
236+ initial = return $ Tuple' IsMap.mapEmpty Nothing
237+
238+ {-# INLINE runFold #-}
239+ runFold kv (Fold step1 initial1 extract1) (k, a) = do
240+ res <- initial1
241+ case res of
242+ Partial s -> do
243+ res1 <- step1 s a
244+ return
245+ $ case res1 of
246+ Partial _ ->
247+ let fld = Fold step1 (return res1) extract1
248+ in Tuple' (IsMap.mapInsert k fld kv) Nothing
249+ Done b -> Tuple' (IsMap.mapDelete k kv) (Just (k, b))
250+ Done b -> return $ Tuple' kv (Just (k, b))
251+
252+ step (Tuple' kv _) a = do
253+ let k = getKey a
254+ case IsMap.mapLookup k kv of
255+ Nothing -> do
256+ fld <- getFold a
257+ runFold kv fld (k, a)
258+ Just f -> runFold kv f (k, a)
259+
260+ extract (Tuple' kv x) = (Prelude.mapM f kv, x)
261+
262+ where
263+
264+ f (Fold _ i e) = do
265+ r <- i
266+ case r of
267+ Partial s -> e s
268+ Done b -> return b
269+
270+-- | This is specialized version of 'demuxScanWith' that uses mutable cells as
271+-- fold accumulators for better performance.
272+--
273+-- Definition:
274+--
275+-- >>> demuxScanMutWith = Fold.demuxScanWith
276+--
277+{-# INLINE demuxScanMutWith #-}
278+demuxScanMutWith :: (MonadIO m, IsMap f, Traversable f) =>
279+ (a -> Key f)
280+ -> (a -> m (Fold m a b))
281+ -> Fold m a (m (f b), Maybe (Key f, b))
282+demuxScanMutWith getKey getFold = fmap extract $ foldlM' step initial
283+
284+ where
285+
286+ initial = return $ Tuple' IsMap.mapEmpty Nothing
287+
288+ {-# INLINE initFold #-}
289+ initFold kv (Fold step1 initial1 extract1) (k, a) = do
290+ res <- initial1
291+ case res of
292+ Partial s -> do
293+ res1 <- step1 s a
294+ case res1 of
295+ Partial _ -> do
296+ let fld = Fold step1 (return res1) extract1
297+ ref <- liftIO $ newIORef fld
298+ return $ Tuple' (IsMap.mapInsert k ref kv) Nothing
299+ Done b -> return $ Tuple' kv (Just (k, b))
300+ Done b -> return $ Tuple' kv (Just (k, b))
301+
302+ {-# INLINE runFold #-}
303+ runFold kv ref (Fold step1 initial1 extract1) (k, a) = do
304+ res <- initial1
305+ case res of
306+ Partial s -> do
307+ res1 <- step1 s a
308+ case res1 of
309+ Partial _ -> do
310+ let fld = Fold step1 (return res1) extract1
311+ liftIO $ writeIORef ref fld
312+ return $ Tuple' kv Nothing
313+ Done b ->
314+ let kv1 = IsMap.mapDelete k kv
315+ in return $ Tuple' kv1 (Just (k, b))
316+ Done _ -> error "demuxScanMutWith: unreachable"
317+
318+ step (Tuple' kv _) a = do
319+ let k = getKey a
320+ case IsMap.mapLookup k kv of
321+ Nothing -> do
322+ f <- getFold a
323+ initFold kv f (k, a)
324+ Just ref -> do
325+ f <- liftIO $ readIORef ref
326+ runFold kv ref f (k, a)
327+
328+ extract (Tuple' kv x) = (Prelude.mapM f kv, x)
329+
330+ where
331+
332+ f ref = do
333+ (Fold _ i e) <- liftIO $ readIORef ref
334+ r <- i
335+ case r of
336+ Partial s -> e s
337+ Done b -> return b
338+
339+-- | This collects all the results of 'demuxScanWith' in a container.
340+--
341+{-# INLINE demuxWith #-}
342+demuxWith :: (Monad m, IsMap f, Traversable f) =>
343+ (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (f b)
344+demuxWith getKey getFold =
345+ let
346+ classifier = demuxScanWith getKey getFold
347+ getMap Nothing = pure IsMap.mapEmpty
348+ getMap (Just action) = action
349+ aggregator =
350+ teeWith IsMap.mapUnion
351+ (rmapM getMap $ lmap fst latest)
352+ (lmap snd $ catMaybes toMap)
353+ in postscan classifier aggregator
354+
355+-- | Same as 'demuxWith' but uses 'demuxScanMutWith' for better performance.
356+--
357+-- Definition:
358+--
359+-- >>> demuxMutWith = Fold.demuxWith
360+--
361+{-# INLINE demuxMutWith #-}
362+demuxMutWith :: (MonadIO m, IsMap f, Traversable f) =>
363+ (a -> Key f) -> (a -> m (Fold m a b)) -> Fold m a (f b)
364+demuxMutWith getKey getFold =
365+ let
366+ classifier = demuxScanMutWith getKey getFold
367+ getMap Nothing = pure IsMap.mapEmpty
368+ getMap (Just action) = action
369+ aggregator =
370+ teeWith IsMap.mapUnion
371+ (rmapM getMap $ lmap fst latest)
372+ (lmap snd $ catMaybes toMap)
373+ in postscan classifier aggregator
374+
375+-- | Fold a stream of key value pairs using a function that maps keys to folds.
376+--
377+-- Definition:
378+--
379+-- >>> demux f = Fold.demuxWith fst (Fold.lmap snd . f)
380+--
381+-- Example:
382+--
383+-- >>> import Data.Map (Map)
384+-- >>> :{
385+-- let f "SUM" = return Fold.sum
386+-- f _ = return Fold.product
387+-- input = Stream.fromList [("SUM",1),("PRODUCT",2),("SUM",3),("PRODUCT",4)]
388+-- in Stream.fold (Fold.demux f) input :: IO (Map String Int)
389+-- :}
390+-- fromList [("PRODUCT",8),("SUM",4)]
391+--
392+-- /Pre-release/
393+{-# INLINE demux #-}
394+demux :: (Monad m, IsMap f, Traversable f) =>
395+ (Key f -> m (Fold m a b)) -> Fold m (Key f, a) (f b)
396+demux f = demuxWith fst (\(k, _) -> fmap (lmap snd) (f k))
397+
398+------------------------------------------------------------------------------
399+-- Classify: Like demux but uses the same fold for all keys.
400+------------------------------------------------------------------------------
401+
402+-- XXX Change these to make the behavior similar to demux* variants. We can
403+-- implement this using classifyScanManyWith. Maintain a set of done folds in
404+-- the underlying monad, and when initial is called look it up, if the fold is
405+-- done then initial would set a flag in the state to ignore the input or
406+-- return an error.
407+
408+-- | Folds the values for each key using the supplied fold. When scanning, as
409+-- soon as the fold is complete, its result is available in the second
410+-- component of the tuple. The first component of the tuple is a snapshot of
411+-- the in-progress folds.
412+--
413+-- Once the fold for a key is done, any future values of the key are ignored.
414+--
415+-- Definition:
416+--
417+-- >>> classifyScanWith f fld = Fold.demuxScanWith f (const fld)
418+--
419+{-# INLINE classifyScanWith #-}
420+classifyScanWith :: (Monad m, IsMap f, Traversable f, Ord (Key f)) =>
421+ -- Note: we need to return the Map itself to display the in-progress values
422+ -- e.g. to implement top. We could possibly create a separate abstraction
423+ -- for that use case. We return an action because we want it to be lazy so
424+ -- that the downstream consumers can choose to process or discard it.
425+ (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))
426+classifyScanWith f (Fold step1 initial1 extract1) =
427+ fmap extract $ foldlM' step initial
428+
429+ where
430+
431+ initial = return $ Tuple3' IsMap.mapEmpty Set.empty Nothing
432+
433+ {-# INLINE initFold #-}
434+ initFold kv set k a = do
435+ x <- initial1
436+ case x of
437+ Partial s -> do
438+ r <- step1 s a
439+ return
440+ $ case r of
441+ Partial s1 ->
442+ Tuple3' (IsMap.mapInsert k s1 kv) set Nothing
443+ Done b ->
444+ Tuple3' kv set (Just (k, b))
445+ Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b)))
446+
447+ step (Tuple3' kv set _) a = do
448+ let k = f a
449+ case IsMap.mapLookup k kv of
450+ Nothing -> do
451+ if Set.member k set
452+ then return (Tuple3' kv set Nothing)
453+ else initFold kv set k a
454+ Just s -> do
455+ r <- step1 s a
456+ return
457+ $ case r of
458+ Partial s1 ->
459+ Tuple3' (IsMap.mapInsert k s1 kv) set Nothing
460+ Done b ->
461+ let kv1 = IsMap.mapDelete k kv
462+ in Tuple3' kv1 (Set.insert k set) (Just (k, b))
463+
464+ extract (Tuple3' kv _ x) = (Prelude.mapM extract1 kv, x)
465+
466+-- XXX we can use a Prim IORef if we can constrain the state "s" to be Prim
467+--
468+-- The code is almost the same as classifyScanWith except the IORef operations.
469+
470+-- | Same as classifyScanWith except that it uses mutable IORef cells in the
471+-- Map providing better performance. Be aware that if this is used as a scan,
472+-- the values in the intermediate Maps would be mutable.
473+--
474+-- Definitions:
475+--
476+-- >>> classifyScanMutWith = Fold.classifyScanWith
477+-- >>> classifyScanMutWith f fld = Fold.demuxScanMutWith f (const fld)
478+--
479+{-# INLINE classifyScanMutWith #-}
480+classifyScanMutWith :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>
481+ (a -> Key f) -> Fold m a b -> Fold m a (m (f b), Maybe (Key f, b))
482+classifyScanMutWith f (Fold step1 initial1 extract1) =
483+ fmap extract $ foldlM' step initial
484+
485+ where
486+
487+ initial = return $ Tuple3' IsMap.mapEmpty Set.empty Nothing
488+
489+ {-# INLINE initFold #-}
490+ initFold kv set k a = do
491+ x <- initial1
492+ case x of
493+ Partial s -> do
494+ r <- step1 s a
495+ case r of
496+ Partial s1 -> do
497+ ref <- liftIO $ newIORef s1
498+ return $ Tuple3' (IsMap.mapInsert k ref kv) set Nothing
499+ Done b ->
500+ return $ Tuple3' kv set (Just (k, b))
501+ Done b -> return (Tuple3' kv (Set.insert k set) (Just (k, b)))
502+
503+ step (Tuple3' kv set _) a = do
504+ let k = f a
505+ case IsMap.mapLookup k kv of
506+ Nothing -> do
507+ if Set.member k set
508+ then return (Tuple3' kv set Nothing)
509+ else initFold kv set k a
510+ Just ref -> do
511+ s <- liftIO $ readIORef ref
512+ r <- step1 s a
513+ case r of
514+ Partial s1 -> do
515+ liftIO $ writeIORef ref s1
516+ return $ Tuple3' kv set Nothing
517+ Done b ->
518+ let kv1 = IsMap.mapDelete k kv
519+ in return
520+ $ Tuple3' kv1 (Set.insert k set) (Just (k, b))
521+
522+ extract (Tuple3' kv _ x) =
523+ (Prelude.mapM (\ref -> liftIO (readIORef ref) >>= extract1) kv, x)
524+
525+-- | Fold a key value stream to a key-value container. If the same key appears
526+-- multiple times, only the last value is retained.
527+{-# INLINE toMap #-}
528+toMap :: (Monad m, IsMap f) => Fold m (Key f, a) (f a)
529+toMap = foldl' (\kv (k, v) -> IsMap.mapInsert k v kv) IsMap.mapEmpty
530+
531+-- | Split the input stream based on a key field and fold each split using the
532+-- given fold. Useful for map/reduce, bucketizing the input in different bins
533+-- or for generating histograms.
534+--
535+-- Example:
536+--
537+-- >>> import Data.Map.Strict (Map)
538+-- >>> :{
539+-- let input = Stream.fromList [("ONE",1),("ONE",1.1),("TWO",2), ("TWO",2.2)]
540+-- classify = Fold.classifyWith fst (Fold.lmap snd Fold.toList)
541+-- in Stream.fold classify input :: IO (Map String [Double])
542+-- :}
543+-- fromList [("ONE",[1.0,1.1]),("TWO",[2.0,2.2])]
544+--
545+-- Once the classifier fold terminates for a particular key any further inputs
546+-- in that bucket are ignored.
547+--
548+-- Space used is proportional to the number of keys seen till now and
549+-- monotonically increases because it stores whether a key has been seen or
550+-- not.
551+--
552+-- /Stops: never/
553+--
554+-- /Pre-release/
555+--
556+{-# INLINE classifyWith #-}
557+classifyWith :: (Monad m, IsMap f, Traversable f, Ord (Key f)) =>
558+ (a -> Key f) -> Fold m a b -> Fold m a (f b)
559+classifyWith f fld =
560+ let
561+ classifier = classifyScanWith f fld
562+ getMap Nothing = pure IsMap.mapEmpty
563+ getMap (Just action) = action
564+ aggregator =
565+ teeWith IsMap.mapUnion
566+ (rmapM getMap $ lmap fst latest)
567+ (lmap snd $ catMaybes toMap)
568+ in postscan classifier aggregator
569+
570+-- | Same as 'classifyWith' but maybe faster because it uses mutable cells as
571+-- fold accumulators in the Map.
572+--
573+-- Definition:
574+--
575+-- >>> classifyMutWith = Fold.classifyWith
576+--
577+{-# INLINE classifyMutWith #-}
578+classifyMutWith :: (MonadIO m, IsMap f, Traversable f, Ord (Key f)) =>
579+ (a -> Key f) -> Fold m a b -> Fold m a (f b)
580+classifyMutWith f fld =
581+ let
582+ classifier = classifyScanMutWith f fld
583+ getMap Nothing = pure IsMap.mapEmpty
584+ getMap (Just action) = action
585+ aggregator =
586+ teeWith IsMap.mapUnion
587+ (rmapM getMap $ lmap fst latest)
588+ (lmap snd $ catMaybes toMap)
589+ in postscan classifier aggregator
590+
591+-- | Given an input stream of key value pairs and a fold for values, fold all
592+-- the values belonging to each key. Useful for map/reduce, bucketizing the
593+-- input in different bins or for generating histograms.
594+--
595+-- Definition:
596+--
597+-- >>> classify = Fold.classifyWith fst . Fold.lmap snd
598+--
599+-- Example:
600+--
601+-- >>> :{
602+-- let input = Stream.fromList [("ONE",1),("ONE",1.1),("TWO",2), ("TWO",2.2)]
603+-- in Stream.fold (Fold.classify Fold.toList) input
604+-- :}
605+-- fromList [("ONE",[1.0,1.1]),("TWO",[2.0,2.2])]
606+--
607+-- /Pre-release/
608+{-# INLINE classify #-}
609+classify :: (Monad m, Ord k) => Fold m a b -> Fold m (k, a) (Map k b)
610+classify = classifyWith fst . lmap snd
611+
612+-- | Determine the frequency of each element in the stream.
613+--
614+-- You can just collect the keys of the resulting map to get the unique
615+-- elements in the stream.
616+--
617+-- Definition:
618+--
619+-- >>> frequency = Fold.classifyWith id Fold.length
620+--
621+{-# INLINE frequency #-}
622+frequency :: (Monad m, Ord a) => Fold m a (Map a Int)
623+frequency = classifyWith id length
core/src/Streamly/Internal/Data/IsMap.hs → src/Streamly/Internal/Data/IsMap.hsrenamed

The content is the same.

src/Streamly/Internal/Data/Stream/Exception/Lifted.hs+104−7
@@ -12,29 +12,32 @@ module Streamly.Internal.Data.Stream.Exception.Lifted
1212 , bracket
1313 , bracket3
1414 , finally
15+ , retry
1516
1617 -- For IsStream module
1718 , afterD
1819 , bracket3D
20+ , retryD
1921 )
2022where
2123
2224#include "inline.hs"
2325
24−import Control.Exception (SomeException, mask_)
26+import Control.Exception (Exception, SomeException, mask_)
2527import Control.Monad.Catch (MonadCatch)
28+import Data.Map.Strict (Map)
2629import GHC.Exts (inline)
2730import Streamly.Internal.Control.Concurrent
2831 (MonadRunInIO, MonadAsync, withRunInIO)
2932import Streamly.Internal.Data.Stream.Type (Stream, fromStreamD, toStreamD)
3033import Streamly.Internal.Data.IOFinalizer.Lifted
3134 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
35+import Streamly.Internal.Data.Stream.StreamD (Step(..))
3236
3337import qualified Control.Monad.Catch as MC
38+import qualified Data.Map.Strict as Map
3439import qualified Streamly.Internal.Data.Stream.StreamD as D
3540
36−import Streamly.Internal.Data.Stream.StreamD.Type hiding (Stream)
37−
3841-- $setup
3942-- >>> :m
4043-- >>> import qualified Streamly.Internal.Data.Stream.Exception.Lifted as Stream
@@ -76,7 +79,7 @@ gbracket bef aft onExc onGC ftry action =
7679 return (r, ref)
7780 return $ Skip $ GBracketIONormal (action r) r ref
7881
79− step gst (GBracketIONormal (UnStream step1 st) v ref) = do
82+ step gst (GBracketIONormal (D.UnStream step1 st) v ref) = do
8083 res <- ftry $ step1 gst st
8184 case res of
8285 Right r -> case r of
@@ -93,9 +96,9 @@ gbracket bef aft onExc onGC ftry action =
9396 -- the finalizer and have not run the exception handler then we
9497 -- may leak the resource.
9598 stream <-
96− clearingIOFinalizer ref (onExc v e (UnStream step1 st))
99+ clearingIOFinalizer ref (onExc v e (D.UnStream step1 st))
97100 return $ Skip (GBracketIOException stream)
98− step gst (GBracketIOException (UnStream step1 st)) = do
101+ step gst (GBracketIOException (D.UnStream step1 st)) = do
99102 res <- step1 gst st
100103 case res of
101104 Yield x s ->
@@ -115,7 +118,7 @@ bracket3D bef aft onExc onGC =
115118 gbracket
116119 bef
117120 aft
118− (\a (e :: SomeException) _ -> onExc a >> return (nilM (MC.throwM e)))
121+ (\a (e :: SomeException) _ -> onExc a >> return (D.nilM (MC.throwM e)))
119122 onGC
120123 (inline MC.try)
121124
@@ -222,3 +225,97 @@ afterD action (D.Stream step state) = D.Stream step' Nothing
222225{-# INLINE after #-}
223226after :: MonadRunInIO m => m b -> Stream m a -> Stream m a
224227after action xs = fromStreamD $ afterD action $ toStreamD xs
228+
229+data RetryState emap s1 s2
230+ = RetryWithMap emap s1
231+ | RetryDefault s2
232+
233+-- | See 'Streamly.Internal.Data.Stream.retry'
234+--
235+{-# INLINE_NORMAL retryD #-}
236+retryD
237+ :: forall e m a. (Exception e, Ord e, MonadCatch m)
238+ => Map e Int
239+ -- ^ map from exception to retry count
240+ -> (e -> D.Stream m a)
241+ -- ^ default handler for those exceptions that are not in the map
242+ -> D.Stream m a
243+ -> D.Stream m a
244+retryD emap0 defaultHandler (D.Stream step0 state0) = D.Stream step state
245+
246+ where
247+
248+ state = RetryWithMap emap0 state0
249+
250+ {-# INLINE_LATE step #-}
251+ step gst (RetryWithMap emap st) = do
252+ eres <- MC.try $ step0 gst st
253+ case eres of
254+ Left e -> handler e emap st
255+ Right res ->
256+ return
257+ $ case res of
258+ Yield x st1 -> Yield x $ RetryWithMap emap st1
259+ Skip st1 -> Skip $ RetryWithMap emap st1
260+ Stop -> Stop
261+ step gst (RetryDefault (D.UnStream step1 state1)) = do
262+ res <- step1 gst state1
263+ return
264+ $ case res of
265+ Yield x st1 -> Yield x $ RetryDefault (D.Stream step1 st1)
266+ Skip st1 -> Skip $ RetryDefault (D.Stream step1 st1)
267+ Stop -> Stop
268+
269+ {-# INLINE handler #-}
270+ handler e emap st =
271+ return
272+ $ Skip
273+ $ case Map.lookup e emap of
274+ Just i
275+ | i > 0 ->
276+ let emap1 = Map.insert e (i - 1) emap
277+ in RetryWithMap emap1 st
278+ | otherwise -> RetryDefault $ defaultHandler e
279+ Nothing -> RetryDefault $ defaultHandler e
280+
281+-- | @retry@ takes 3 arguments
282+--
283+-- 1. A map @m@ whose keys are exceptions and values are the number of times to
284+-- retry the action given that the exception occurs.
285+--
286+-- 2. A handler @han@ that decides how to handle an exception when the exception
287+-- cannot be retried.
288+--
289+-- 3. The stream itself that we want to run this mechanism on.
290+--
291+-- When evaluating a stream if an exception occurs,
292+--
293+-- 1. The stream evaluation aborts
294+--
295+-- 2. The exception is looked up in @m@
296+--
297+-- a. If the exception exists and the mapped value is > 0 then,
298+--
299+-- i. The value is decreased by 1.
300+--
301+-- ii. The stream is resumed from where the exception was called, retrying
302+-- the action.
303+--
304+-- b. If the exception exists and the mapped value is == 0 then the stream
305+-- evaluation stops.
306+--
307+-- c. If the exception does not exist then we handle the exception using
308+-- @han@.
309+--
310+-- /Internal/
311+--
312+{-# INLINE retry #-}
313+retry :: (MonadCatch m, Exception e, Ord e)
314+ => Map e Int
315+ -- ^ map from exception to retry count
316+ -> (e -> Stream m a)
317+ -- ^ default handler for those exceptions that are not in the map
318+ -> Stream m a
319+ -> Stream m a
320+retry emap handler inp =
321+ fromStreamD $ retryD emap (toStreamD . handler) $ toStreamD inp
src/Streamly/Internal/Data/Stream/Extra.hsnew file+164
@@ -0,0 +1,164 @@
1+-- |
2+-- Module : Streamly.Internal.Data.Stream.Extra
3+-- Copyright : (c) 2019 Composewell Technologies
4+-- License : BSD-3-Clause
5+-- Maintainer : streamly@composewell.com
6+-- Stability : experimental
7+-- Portability : GHC
8+
9+module Streamly.Internal.Data.Stream.Extra
10+ (
11+ nub
12+ , joinInnerMap
13+ , joinLeftMap
14+ , joinOuterMap
15+ )
16+where
17+
18+#include "inline.hs"
19+
20+import Control.Monad.IO.Class (MonadIO)
21+import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))
22+import Streamly.Internal.Data.Stream.Type (Stream)
23+
24+import qualified Data.Map.Strict as Map
25+import qualified Data.Set as Set
26+import qualified Streamly.Data.Fold as Fold
27+import qualified Streamly.Data.Stream as Stream
28+import qualified Streamly.Internal.Data.Stream as Stream (concatM)
29+import qualified Streamly.Internal.Data.Stream.StreamD as D
30+
31+-- $setup
32+-- >>> :m
33+-- >>> import qualified Streamly.Internal.Data.Stream as Stream
34+
35+-- | The memory used is proportional to the number of unique elements in the
36+-- stream. If we want to limit the memory we can just use "take" to limit the
37+-- uniq elements in the stream.
38+{-# INLINE_NORMAL nub #-}
39+nub :: (Monad m, Ord a) => D.Stream m a -> D.Stream m a
40+nub (D.Stream step1 state1) = D.Stream step (Set.empty, state1)
41+
42+ where
43+
44+ step gst (set, st) = do
45+ r <- step1 gst st
46+ return
47+ $ case r of
48+ Yield x s ->
49+ if Set.member x set
50+ then Skip (set, s)
51+ else Yield x (Set.insert x set, s)
52+ Skip s -> Skip (set, s)
53+ Stop -> Stop
54+
55+-- XXX Generate error if a duplicate insertion is attempted?
56+toMap :: (Monad m, Ord k) => Stream m (k, v) -> m (Map.Map k v)
57+toMap =
58+ let f = Fold.foldl' (\kv (k, b) -> Map.insert k b kv) Map.empty
59+ in Stream.fold f
60+
61+-- If the second stream is too big it can be partitioned based on hashes and
62+-- then we can process one parition at a time.
63+--
64+-- XXX An IntMap may be faster when the keys are Int.
65+-- XXX Use hashmap instead of map?
66+--
67+-- | Like 'joinInner' but uses a 'Map' for efficiency.
68+--
69+-- If the input streams have duplicate keys, the behavior is undefined.
70+--
71+-- For space efficiency use the smaller stream as the second stream.
72+--
73+-- Space: O(n)
74+--
75+-- Time: O(m + n)
76+--
77+-- /Pre-release/
78+{-# INLINE joinInnerMap #-}
79+joinInnerMap :: (Monad m, Ord k) =>
80+ Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, b)
81+joinInnerMap s1 s2 =
82+ Stream.concatM $ do
83+ km <- toMap s2
84+ pure $ Stream.mapMaybe (joinAB km) s1
85+
86+ where
87+
88+ joinAB kvm (k, a) =
89+ case k `Map.lookup` kvm of
90+ Just b -> Just (k, a, b)
91+ Nothing -> Nothing
92+
93+-- | Like 'joinLeft' but uses a hashmap for efficiency.
94+--
95+-- Space: O(n)
96+--
97+-- Time: O(m + n)
98+--
99+-- /Pre-release/
100+{-# INLINE joinLeftMap #-}
101+joinLeftMap :: (Ord k, Monad m) =>
102+ Stream m (k, a) -> Stream m (k, b) -> Stream m (k, a, Maybe b)
103+joinLeftMap s1 s2 =
104+ Stream.concatM $ do
105+ km <- toMap s2
106+ return $ fmap (joinAB km) s1
107+
108+ where
109+
110+ joinAB km (k, a) =
111+ case k `Map.lookup` km of
112+ Just b -> (k, a, Just b)
113+ Nothing -> (k, a, Nothing)
114+
115+-- Put the b's that have been paired, in another hash or mutate the hash to set
116+-- a flag. At the end go through @Stream m b@ and find those that are not in that
117+-- hash to return (Nothing, b).
118+--
119+-- | Like 'joinOuter' but uses a 'Map' for efficiency.
120+--
121+-- Space: O(m + n)
122+--
123+-- Time: O(m + n)
124+--
125+-- /Pre-release/
126+{-# INLINE joinOuterMap #-}
127+joinOuterMap ::
128+ (Ord k, MonadIO m) =>
129+ Stream m (k, a) -> Stream m (k, b) -> Stream m (k, Maybe a, Maybe b)
130+joinOuterMap s1 s2 =
131+ Stream.concatM $ do
132+ km1 <- kvFold s1
133+ km2 <- kvFold s2
134+
135+ -- XXX Not sure if toList/fromList would fuse optimally. We may have to
136+ -- create a fused Map.toStream function.
137+ let res1 = fmap (joinAB km2)
138+ $ Stream.fromList $ Map.toList km1
139+ where
140+ joinAB km (k, a) =
141+ case k `Map.lookup` km of
142+ Just b -> (k, Just a, Just b)
143+ Nothing -> (k, Just a, Nothing)
144+
145+ -- XXX We can take advantage of the lookups in the first pass above to
146+ -- reduce the number of lookups in this pass. If we keep mutable cells
147+ -- in the second Map, we can flag it in the first pass and not do any
148+ -- lookup in the second pass if it is flagged.
149+ let res2 = Stream.mapMaybe (joinAB km1)
150+ $ Stream.fromList $ Map.toList km2
151+ where
152+ joinAB km (k, b) =
153+ case k `Map.lookup` km of
154+ Just _ -> Nothing
155+ Nothing -> Just (k, Nothing, Just b)
156+
157+ return $ Stream.append res1 res2
158+
159+ where
160+
161+ -- XXX Generate error if a duplicate insertion is attempted?
162+ kvFold =
163+ let f = Fold.foldl' (\kv (k, b) -> Map.insert k b kv) Map.empty
164+ in Stream.fold f
src/Streamly/Internal/Data/Stream/IsStream/Common.hs+1−1
@@ -280,7 +280,7 @@ relTimesWith = fmap snd . timesWith
280280--
281281-- >>> concatFold = Prelude.foldl Stream.foldContinue Fold.sum
282282--
283−-- >>> fold f = Fold.finish . Stream.foldContinue f
283+-- >>> fold f = Fold.extractM . Stream.foldContinue f
284284--
285285-- /Internal/
286286{-# INLINE foldContinue #-}
src/Streamly/Internal/Data/Stream/IsStream/Exception.hs+2−2
@@ -40,12 +40,12 @@ import qualified Streamly.Internal.Data.Stream.StreamD.Exception as D
4040 , finally_
4141 , ghandle
4242 , handle
43− , retry
4443 )
4544
4645import qualified Streamly.Internal.Data.Stream.Exception.Lifted as D
4746 ( afterD
4847 , bracket3D
48+ , retryD
4949 )
5050
5151
@@ -281,4 +281,4 @@ retry :: (IsStream t, MonadCatch m, Exception e, Ord e)
281281 -> t m a
282282 -> t m a
283283retry emap handler inp =
284− fromStreamD $ D.retry emap (toStreamD . handler) $ toStreamD inp
284+ fromStreamD $ D.retryD emap (toStreamD . handler) $ toStreamD inp
src/Streamly/Internal/Data/Stream/Time.hs+2−2
@@ -709,7 +709,7 @@ classifySessionsByGeneric _ tick reset ejectPred tmout
709709-- session is created for that key.
710710--
711711-- >>> :{
712−-- Stream.fold (Fold.drainBy print)
712+-- Stream.fold (Fold.drainMapM print)
713713-- $ Concur.classifySessionsBy 1 False (const (return False)) 3 (Fold.take 3 Fold.toList)
714714-- $ Stream.timestamped
715715-- $ Stream.delay 0.1
@@ -815,7 +815,7 @@ classifySessionsOf = classifySessionsBy 1 False
815815--
816816{-# INLINE sampleIntervalEnd #-}
817817sampleIntervalEnd :: MonadAsync m => Double -> Stream m a -> Stream m a
818−sampleIntervalEnd n = Stream.catMaybes . intervalsOf n Fold.last
818+sampleIntervalEnd n = Stream.catMaybes . intervalsOf n Fold.latest
819819
820820-- | Like 'sampleInterval' but samples at the beginning of the time window.
821821--
src/Streamly/Internal/FileSystem/Event/Linux.hs+1−1
@@ -713,7 +713,7 @@ addToWatch cfg@Config{..} watch0@(Watch handle wdMap) root0 path0 = do
713713 pathIsDir <- doesDirectoryExist $ utf8ToString absPath
714714 when (watchRec && pathIsDir) $ do
715715 let f = addToWatch cfg watch0 root . appendPaths path
716− in S.fold (FL.drainBy f)
716+ in S.fold (FL.drainMapM f)
717717 $ S.mapM toUtf8
718718 $ Dir.readDirs $ utf8ToString absPath
719719
src/Streamly/Internal/FileSystem/Event/Windows.hs+1−1
@@ -436,7 +436,7 @@ utf8ToStringList = NonEmpty.map utf8ToString
436436closePathHandleStream :: Stream IO (HANDLE, FilePath, Config) -> IO ()
437437closePathHandleStream =
438438 let f (h, _, _) = closeHandle h
439− in S.fold (Fold.drainBy f)
439+ in S.fold (Fold.drainMapM f)
440440
441441-- XXX
442442-- Document the path treatment for Linux/Windows/macOS modules.
src/Streamly/Internal/Network/Inet/TCP.hs+4−5
@@ -1,5 +1,3 @@
1−#include "inline.hs"
2−
31-- |
42-- Module : Streamly.Internal.Network.Inet.TCP
53-- Copyright : (c) 2019 Composewell Technologies
@@ -94,6 +92,8 @@ module Streamly.Internal.Network.Inet.TCP
9492 )
9593where
9694
95+#include "inline.hs"
96+
9797import Control.Exception (onException)
9898import Control.Monad.Catch (MonadCatch, MonadMask, bracket)
9999import Control.Monad.IO.Class (MonadIO(..))
@@ -122,8 +122,7 @@ import qualified Streamly.Data.Fold as FL
122122import qualified Streamly.Data.Unfold as UF
123123import qualified Streamly.Internal.Data.Unfold as UF (bracketIO, first)
124124import qualified Streamly.Internal.Data.Array.Unboxed.Stream as AS
125−import qualified Streamly.Internal.Data.Fold.Type as FL
126− (initialize, snoc, Step(..))
125+import qualified Streamly.Internal.Data.Fold.Type as FL (Step(..))
127126import qualified Streamly.Internal.Data.Stream as S
128127import qualified Streamly.Internal.Data.Stream.Exception.Lifted as S
129128import qualified Streamly.Internal.Network.Socket as ISK
@@ -355,7 +354,7 @@ writeChunks addr port = Fold step initial extract
355354 where
356355 initial = do
357356 skt <- liftIO (connect addr port)
358− fld <- FL.initialize (ISK.writeChunks skt)
357+ fld <- FL.reduce (ISK.writeChunks skt)
359358 `MC.onException` liftIO (Net.close skt)
360359 return $ FL.Partial (Tuple' fld skt)
361360 step (Tuple' fld skt) x = do
src/Streamly/Internal/Network/Socket.hs+2−2
@@ -450,14 +450,14 @@ reader = UF.first defaultChunkSize readerWith
450450{-# INLINE putChunks #-}
451451putChunks :: (MonadIO m, Unbox a)
452452 => Socket -> Stream m (Array a) -> m ()
453−putChunks h = S.fold (FL.drainBy (liftIO . putChunk h))
453+putChunks h = S.fold (FL.drainMapM (liftIO . putChunk h))
454454
455455-- | Write a stream of arrays to a socket. Each array in the stream is written
456456-- to the socket as a separate IO request.
457457--
458458{-# INLINE writeChunks #-}
459459writeChunks :: (MonadIO m, Unbox a) => Socket -> Fold m (Array a) ()
460−writeChunks h = FL.drainBy (liftIO . putChunk h)
460+writeChunks h = FL.drainMapM (liftIO . putChunk h)
461461
462462-- | @writeChunksWith bufsize socket@ writes a stream of arrays to
463463-- @socket@ after coalescing the adjacent arrays in chunks of @bufsize@.
streamly.cabal+4−1
@@ -337,7 +337,8 @@ library
337337 -- dependency order To view dependency graph:
338338 -- graphmod | dot -Tps > deps.ps
339339
340− Streamly.Internal.Data.IsMap.HashMap
340+ Streamly.Internal.Data.IsMap
341+ , Streamly.Internal.Data.IsMap.HashMap
341342
342343 -- streamly-concurrent
343344 , Streamly.Internal.Control.Concurrent
@@ -352,6 +353,7 @@ library
352353 , Streamly.Internal.Data.Fold.Concurrent.Channel.Type
353354 , Streamly.Internal.Data.Fold.Concurrent.Channel
354355 , Streamly.Internal.Data.Fold.Concurrent
356+ , Streamly.Internal.Data.Fold.Extra
355357
356358 , Streamly.Internal.Data.Stream.Concurrent.Channel.Type
357359 , Streamly.Internal.Data.Stream.Concurrent.Channel.Dispatcher
@@ -364,6 +366,7 @@ library
364366 , Streamly.Internal.Data.Stream.Zip.Concurrent
365367 , Streamly.Internal.Data.Stream.Time
366368 , Streamly.Internal.Data.Stream.Exception.Lifted
369+ , Streamly.Internal.Data.Stream.Extra
367370
368371 -- streamly-unicode (depends on unicode-data)
369372 , Streamly.Internal.Unicode.Utf8
test/Streamly/Test/Data/Fold.hs+1
@@ -25,6 +25,7 @@ import qualified Data.Map
2525import qualified Prelude
2626import qualified Streamly.Internal.Data.Array.Unboxed.Mut as MArray
2727import qualified Streamly.Internal.Data.Fold as Fold
28+import qualified Streamly.Internal.Data.Fold.Extra as Fold
2829import qualified Streamly.Internal.Data.Stream as Stream
2930
3031import Prelude hiding