on master help sign in
Update Fold docs, move containers ops to streamly pkg
703823bHarendra Kumar
benchmark/Streamly/Benchmark/Data/Fold.hs+1
@@ -28,6 +28,7 @@ import Streamly.Internal.Data.Fold (Fold(..))
28import Streamly.Internal.Data.IsMap.HashMap ()
28import Streamly.Internal.Data.IsMap.HashMap ()
29
29
30import qualified Streamly.Internal.Data.Fold as FL
30import qualified Streamly.Internal.Data.Fold as FL
31+import qualified Streamly.Internal.Data.Fold.Extra as FL
31import qualified Streamly.Internal.Data.Unfold as Unfold
32import qualified Streamly.Internal.Data.Unfold as Unfold
32import qualified Streamly.Internal.Data.Pipe as Pipe
33import qualified Streamly.Internal.Data.Pipe as Pipe
33import qualified Streamly.Internal.Data.Stream as Stream
34import qualified Streamly.Internal.Data.Stream as Stream
benchmark/Streamly/Benchmark/Data/Stream/Expand.hs+1
@@ -45,6 +45,7 @@ import Streamly.Benchmark.Prelude
45 , sourceFoldMapWithStream, concatFoldableWith, concatForFoldableWith)
45 , sourceFoldMapWithStream, concatFoldableWith, concatForFoldableWith)
46#else
46#else
47import qualified Streamly.Internal.Data.Stream as S
47import qualified Streamly.Internal.Data.Stream as S
48+import qualified Streamly.Internal.Data.Stream.Extra as S
48#endif
49#endif
49import qualified Streamly.Internal.Data.Unfold as UF
50import qualified Streamly.Internal.Data.Unfold as UF
50import qualified Streamly.Internal.Data.Fold as Fold
51import qualified Streamly.Internal.Data.Fold as Fold
core/src/Streamly/Data/Fold.hs+59−25
@@ -15,8 +15,8 @@
15-- >>> import qualified Streamly.Data.Stream as Stream
15-- >>> import qualified Streamly.Data.Stream as Stream
16--
16--
17-- For example, a 'sum' Fold represents adding the input to the accumulated
17-- 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
18+-- sum. A fold driver pushes values from a stream to the 'Fold' one at a time,
19−-- to the 'Fold' one at a time, reducing the stream to a single value.
19+-- reducing the stream to a single value.
20--
20--
21-- >>> Stream.fold Fold.sum $ Stream.fromList [1..100]
21-- >>> Stream.fold Fold.sum $ Stream.fromList [1..100]
22-- 5050
22-- 5050
@@ -93,7 +93,7 @@
93--
93--
94-- We can often use streams or folds to achieve the same goal. However, streams
94-- We can often use streams or folds to achieve the same goal. However, streams
95-- are more efficient in composition of producers (e.g.
95-- 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
97-- more efficient in composition of consumers (e.g. 'serialWith', 'partition'
97-- more efficient in composition of consumers (e.g. 'serialWith', 'partition'
98-- or 'teeWith').
98-- or 'teeWith').
99--
99--
@@ -135,7 +135,8 @@ module Streamly.Data.Fold
135 -- * Constructors
135 -- * Constructors
136 , foldl'
136 , foldl'
137 , foldlM'
137 , foldlM'
138+ , foldl1'
138− , foldr
139+ , foldr'
139
140
140 -- * Folds
141 -- * Folds
141 -- ** Accumulators
142 -- ** Accumulators
@@ -152,17 +153,10 @@ module Streamly.Data.Fold
152
153
153 -- Reducers
154 -- Reducers
154 , drain
155 , drain
155− , drainBy
156+ , drainMapM
156− , the
157− , uniq
158− , last
159 , length
157 , length
160 , sum
158 , sum
161 , product
159 , product
162− , maximumBy
163− , maximum
164− , minimumBy
165− , minimum
166 , mean
160 , mean
167 , rollingHash
161 , rollingHash
168 , rollingHashWithSalt
162 , rollingHashWithSalt
@@ -171,16 +165,38 @@ module Streamly.Data.Fold
171 , toList
165 , toList
172 , toListRev
166 , toListRev
173
167
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+
174 -- ** Terminating Folds
186 -- ** Terminating Folds
175 -- | These are much like lazy right folds.
187 -- | These are much like lazy right folds.
176
188
177− , index
178 , one
189 , one
190+ , null
191+ -- , satisfy
192+ -- , maybe
193+
194+ , index
195+ , the
179 , find
196 , find
180 , lookup
197 , lookup
181 , findIndex
198 , findIndex
182 , elemIndex
199 , elemIndex
183− , null
184 , elem
200 , elem
185 , notElem
201 , notElem
186 , all
202 , all
@@ -188,24 +204,36 @@ module Streamly.Data.Fold
188 , and
204 , and
189 , or
205 , or
190
206
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+
191 -- * Combinators
222 -- * Combinators
192 -- | Combinators are modifiers of folds. In the type @Fold m a b@, @a@ is
223 -- | Combinators are modifiers of folds. In the type @Fold m a b@, @a@ is
193 -- the input type and @b@ is the output type. Transformations can be
224 -- 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,
225+ -- applied either on the input side (contravariant) or on the output side
195− -- combinators are of one of the following general shapes:
226+ -- (covariant). Therefore, combinators are of one of the following general
227+ -- shapes:
196 --
228 --
197 -- * @... -> Fold m a b -> Fold m c b@ (input transformation)
229 -- * @... -> Fold m a b -> Fold m c b@ (input transformation)
198 -- * @... -> Fold m a b -> Fold m a c@ (output transformation)
230 -- * @... -> Fold m a b -> Fold m a c@ (output transformation)
199 --
231 --
200− -- Output transformations are also known as covariant transformations, and
201− -- input transformations are also known as contravariant transformations.
202 -- The input side transformations are more interesting for folds. Most of
232 -- The input side transformations are more interesting for folds. Most of
203 -- the following sections describe the input transformation operations on a
233 -- 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
234+ -- fold. When an operation makes sense on both input and output side we use
207− -- input side operations and the prefix @r@ (for right) for output side
235+ -- the prefix @l@ (for left) for input side operations and the prefix @r@
208− -- operations.
236+ -- (for right) for output side operations.
209
237
210 -- ** Mapping on output
238 -- ** Mapping on output
211 -- | The 'Functor' instance of a fold maps on the output of the fold:
239 -- | The 'Functor' instance of a fold maps on the output of the fold:
@@ -219,7 +247,10 @@ module Streamly.Data.Fold
219 , lmap
247 , lmap
220 , lmapM
248 , lmapM
221
249
222− -- ** Filtering
250+ -- ** Scanning and Filtering
251+ , scan
252+ , postscan
253+ , scanMaybe
223 , filter
254 , filter
224 , filterM
255 , filterM
225
256
@@ -268,6 +299,9 @@ module Streamly.Data.Fold
268 , concatMap
299 , concatMap
269
300
270 -- * Deprecated
301 -- * Deprecated
302+ , foldr
303+ , drainBy
304+ , last
271 , head
305 , head
272 , sequence
306 , sequence
273 , mapM
307 , mapM
@@ -282,7 +316,7 @@ import Prelude
282 notElem, maximum, minimum, head, last, tail, length, null,
316 notElem, maximum, minimum, head, last, tail, length, null,
283 reverse, iterate, init, and, or, lookup, foldr1, (!!),
317 reverse, iterate, init, and, or, lookup, foldr1, (!!),
284 scanl, scanl1, replicate, concatMap, mconcat, foldMap, unzip,
318 scanl, scanl1, replicate, concatMap, mconcat, foldMap, unzip,
285− span, splitAt, break, mapM)
319+ span, splitAt, break, mapM, maybe)
286
320
287import Streamly.Internal.Data.Fold
321import Streamly.Internal.Data.Fold
288
322
core/src/Streamly/Data/Fold/Tee.hs+1−1
@@ -27,7 +27,7 @@
27-- instances of the output types:
27-- instances of the output types:
28--
28--
29-- >>> import Data.Monoid (Sum(..))
29-- >>> import Data.Monoid (Sum(..))
30−-- >>> t = Tee Fold.one <> Tee Fold.last
30+-- >>> t = Tee Fold.one <> Tee Fold.latest
31-- >>> Stream.fold (toFold t) (fmap Sum $ Stream.enumerateFromTo 1.0 100.0)
31-- >>> Stream.fold (toFold t) (fmap Sum $ Stream.enumerateFromTo 1.0 100.0)
32-- Just (Sum {getSum = 101.0})
32-- Just (Sum {getSum = 101.0})
33--
33--
core/src/Streamly/Data/Parser.hs−1
@@ -18,7 +18,6 @@ module Streamly.Data.Parser
18 -- * Parser Type
18 -- * Parser Type
19 Parser
19 Parser
20
20
21− -- XXX Should we use Fold.fromParser instead?
22 -- -- * Downgrade to Fold
21 -- -- * Downgrade to Fold
23 -- , toFold
22 -- , toFold
24
23
core/src/Streamly/Data/Stream.hs+4−1
@@ -242,7 +242,10 @@ module Streamly.Data.Stream
242 , fold -- XXX rename to run? We can have a Stream.run and Fold.run.
242 , fold -- XXX rename to run? We can have a Stream.run and Fold.run.
243 -- XXX fold1 can be achieved using Monoids or Refolds.
243 -- XXX fold1 can be achieved using Monoids or Refolds.
244 , foldBreak
244 , foldBreak
245− , foldContinue
245+
246+ -- * Builders
247+ , build
248+ -- , buildl
246
249
247 -- ** Parsing
250 -- ** Parsing
248 , parse
251 , parse
core/src/Streamly/Internal/Data/Fold.hs+378−726
@@ -8,12 +8,13 @@
8-- Portability : GHC
8-- Portability : GHC
9--
9--
10-- See "Streamly.Data.Fold" for an overview and
10-- See "Streamly.Data.Fold" for an overview and
11−-- "Streamly.Internal.Data.Fold.Types" for design notes.
11+-- "Streamly.Internal.Data.Fold.Type" for design notes.
12−--
13−-- IMPORTANT: keep the signatures consistent with the folds in Streamly.Prelude
14
12
15module Streamly.Internal.Data.Fold
13module Streamly.Internal.Data.Fold
16 (
14 (
15+ -- * Imports
16+ -- $setup
17+
17 -- * Fold Type
18 -- * Fold Type
18 Step (..)
19 Step (..)
19 , Fold (..)
20 , Fold (..)
@@ -22,19 +23,17 @@ module Streamly.Internal.Data.Fold
22 -- | Which constructor to use?
23 -- | Which constructor to use?
23 --
24 --
24 -- * @foldl*@: If the fold never terminates i.e. does not use the 'Done'
25 -- * @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.
26 -- * @*M@: Use the @M@ suffix variants if any of the step, initial, or
27 -- * @*M@: Use the @M@ suffix variants if any of the step, initial, or
27 -- extract function is monadic, otherwise use the pure variants.
28 -- extract function is monadic, otherwise use the pure variants.
28− -- * @*_@: Use the @_@ suffix variants if the extract function is 'id' or
29− -- 'return'.
30 --
29 --
31 , foldl'
30 , foldl'
32 , foldlM'
31 , foldlM'
33 , foldl1'
32 , foldl1'
34 , foldt'
33 , foldt'
35 , foldtM'
34 , foldtM'
36− , foldr
35+ , foldr'
37− , foldrM
36+ , foldrM'
38
37
39 -- * Mappers
38 -- * Mappers
40 -- | Monadic functions useful with mapM/lmapM on folds or streams.
39 -- | Monadic functions useful with mapM/lmapM on folds or streams.
@@ -42,9 +41,6 @@ module Streamly.Internal.Data.Fold
42 , trace
41 , trace
43
42
44 -- * Folds
43 -- * Folds
45− -- ** Identity
46− , fromPure
47− , fromEffect
48
44
49 -- ** Accumulators
45 -- ** Accumulators
50 -- *** Semigroups and Monoids
46 -- *** Semigroups and Monoids
@@ -55,15 +51,13 @@ module Streamly.Internal.Data.Fold
55
51
56 -- *** Reducers
52 -- *** Reducers
57 , drain
53 , drain
58− , drainBy
54+ , drainMapM
59 , the
55 , the
60 , length
56 , length
61− , genericLength
57+ , lengthGeneric
62− , countDistinct
63− , countDistinctInt
64− , frequency
65 , mean
58 , mean
66 , rollingHash
59 , rollingHash
60+ , defaultSalt
67 , rollingHashWithSalt
61 , rollingHashWithSalt
68 , rollingHashFirstN
62 , rollingHashFirstN
69 -- , rollingHashLastN
63 -- , rollingHashLastN
@@ -88,8 +82,7 @@ module Streamly.Internal.Data.Fold
88 -- $toListRev
82 -- $toListRev
89 , toStream
83 , toStream
90 , toStreamRev
84 , toStreamRev
91− , toMap
85+ , bottomBy
92− , topBy
93 , top
86 , top
94 , bottom
87 , bottom
95
88
@@ -98,7 +91,7 @@ module Streamly.Internal.Data.Fold
98 -- the 'scanMaybe' combinator. For scanners the result of the fold is
91 -- the 'scanMaybe' combinator. For scanners the result of the fold is
99 -- usually a transformation of the current element rather than an
92 -- usually a transformation of the current element rather than an
100 -- aggregation of all elements till now.
93 -- aggregation of all elements till now.
101− , last -- XXX prev
94+ , latest
102 -- , nthLast -- using Ring array
95 -- , nthLast -- using Ring array
103 , indexingWith
96 , indexingWith
104 , indexing
97 , indexing
@@ -109,8 +102,6 @@ module Streamly.Internal.Data.Fold
109 -- | Useful in combination with the 'scanMaybe' combinator.
102 -- | Useful in combination with the 'scanMaybe' combinator.
110 , filtering
103 , filtering
111 , deleteBy
104 , deleteBy
112− , nub
113− , nubInt
114 , uniqBy
105 , uniqBy
115 , uniq
106 , uniq
116 , repeated
107 , repeated
@@ -118,18 +109,25 @@ module Streamly.Internal.Data.Fold
118 , elemIndices
109 , elemIndices
119
110
120 -- ** Terminating Folds
111 -- ** Terminating Folds
121− -- Element folds. Terminate after inspecting one element. All these can be
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
122− -- implemented in terms of the 'maybe' fold.
119+ -- these can be implemented in terms of the 'maybe' fold.
123 , one
120 , one
124 , null
121 , null
125 , satisfy
122 , satisfy
126 , maybe
123 , maybe
127
124
125+ -- *** Multi folds
128− -- Sequence folds. Terminate after inspecting a sequence of elements.
126+ -- | Terminate after consuming one or more elements.
129 , drainN
127 , drainN
130 -- , lastN
128 -- , lastN
131 -- , (!!)
129 -- , (!!)
132− , genericIndex
130+ , indexGeneric
133 , index
131 , index
134 , findM
132 , findM
135 , find
133 , find
@@ -155,6 +153,20 @@ module Streamly.Internal.Data.Fold
155 , droppingWhile
153 , droppingWhile
156 , prune
154 , prune
157
155
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+
158 -- * Combinators
170 -- * Combinators
159 -- ** Utilities
171 -- ** Utilities
160 , with
172 , with
@@ -182,8 +194,8 @@ module Streamly.Internal.Data.Fold
182 , indexed
194 , indexed
183
195
184 -- ** Zipping Input
196 -- ** Zipping Input
185− , zipWithM
197+ , zipStreamWithM
186− , zip
198+ , zipStream
187
199
188 -- ** Filtering Input
200 -- ** Filtering Input
189 , catMaybes
201 , catMaybes
@@ -260,41 +272,6 @@ module Streamly.Internal.Data.Fold
260 , partitionBy
272 , partitionBy
261 , partition
273 , partition
262
274
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−
298 -- ** Splitting
275 -- ** Splitting
299 , many
276 , many
300 , chunksOf
277 , chunksOf
@@ -304,20 +281,12 @@ module Streamly.Internal.Data.Fold
304 , unfoldMany
281 , unfoldMany
305 , concatSequence
282 , concatSequence
306 , concatMap
283 , 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
317 , duplicate
284 , duplicate
318− , finish
319
285
320 -- * Deprecated
286 -- * Deprecated
287+ , foldr
288+ , drainBy
289+ , last
321 , head
290 , head
322 , sequence
291 , sequence
323 , mapM
292 , mapM
@@ -336,11 +305,8 @@ import Data.Bits (shiftL, shiftR, (.|.), (.&.))
336import Data.Either (isLeft, isRight, fromLeft, fromRight)
305import Data.Either (isLeft, isRight, fromLeft, fromRight)
337import Data.Functor.Identity (Identity(..))
306import Data.Functor.Identity (Identity(..))
338import Data.Int (Int64)
307import Data.Int (Int64)
339−import Data.IORef (newIORef, readIORef, writeIORef)
340−import Data.Map.Strict (Map)
341import Data.Word (Word32)
308import Data.Word (Word32)
342import Foreign.Storable (peek, sizeOf)
309import Foreign.Storable (peek, sizeOf)
343−import Streamly.Internal.Data.IsMap (IsMap(..))
344import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)
310import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)
345import Streamly.Internal.Data.Pipe.Type (Pipe (..), PipeState(..))
311import Streamly.Internal.Data.Pipe.Type (Pipe (..), PipeState(..))
346import Streamly.Internal.Data.Unboxed (Unbox)
312import Streamly.Internal.Data.Unboxed (Unbox)
@@ -348,16 +314,14 @@ import Streamly.Internal.Data.Unfold.Type (Unfold(..))
348import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))
314import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))
349import Streamly.Internal.Data.Stream.Type (Stream)
315import Streamly.Internal.Data.Stream.Type (Stream)
350
316
351−import qualified Data.IntSet as IntSet
352−import qualified Data.Set as Set
353import qualified Prelude
317import qualified Prelude
354import qualified Streamly.Internal.Data.Array.Unboxed.Mut.Type as MA
318import qualified Streamly.Internal.Data.Array.Unboxed.Mut.Type as MA
355import qualified Streamly.Internal.Data.Array.Unboxed.Type as Array
319import qualified Streamly.Internal.Data.Array.Unboxed.Type as Array
356import qualified Streamly.Internal.Data.Fold.Window as FoldW
320import qualified Streamly.Internal.Data.Fold.Window as FoldW
357−import qualified Streamly.Internal.Data.IsMap as IsMap
358import qualified Streamly.Internal.Data.Pipe.Type as Pipe
321import qualified Streamly.Internal.Data.Pipe.Type as Pipe
359import qualified Streamly.Internal.Data.Ring.Unboxed as Ring
322import qualified Streamly.Internal.Data.Ring.Unboxed as Ring
360import qualified Streamly.Internal.Data.Stream.StreamD.Type as StreamD
323import qualified Streamly.Internal.Data.Stream.StreamD.Type as StreamD
324+import qualified Streamly.Internal.Data.Stream.StreamK as K
361import qualified Streamly.Internal.Data.Stream.Type as Stream
325import qualified Streamly.Internal.Data.Stream.Type as Stream
362
326
363import Prelude hiding
327import Prelude hiding
@@ -371,7 +335,13 @@ import Streamly.Internal.Data.Fold.Type
371
335
372-- $setup
336-- $setup
373-- >>> :m
337-- >>> :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)
375-- >>> import qualified Streamly.Data.Array.Unboxed as Array
345-- >>> import qualified Streamly.Data.Array.Unboxed as Array
376-- >>> import qualified Streamly.Data.Fold as Fold
346-- >>> import qualified Streamly.Data.Fold as Fold
377-- >>> import qualified Streamly.Data.Stream as Stream
347-- >>> import qualified Streamly.Data.Stream as Stream
@@ -382,8 +352,60 @@ import Streamly.Internal.Data.Fold.Type
382-- >>> import qualified Streamly.Internal.Data.Parser as Parser
352-- >>> import qualified Streamly.Internal.Data.Parser as Parser
383-- >>> import qualified Streamly.Internal.Data.Stream.Type as Stream
353-- >>> import qualified Streamly.Internal.Data.Stream.Type as Stream
384-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
354-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
385−-- >>> import Streamly.Internal.Data.Stream.Type (Stream)
355+-- >>> import Prelude hiding (break, map, span, splitAt)
386−-- >>> import Data.IORef (newIORef, readIORef, writeIORef)
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
387
409
388------------------------------------------------------------------------------
410------------------------------------------------------------------------------
389-- hoist
411-- hoist
@@ -398,7 +420,7 @@ hoist f (Fold step initial extract) =
398
420
399-- | Adapt a pure fold to any monad
421-- | Adapt a pure fold to any monad
400--
422--
401−-- > generally = Fold.hoist (return . runIdentity)
423+-- >>> generally = Fold.hoist (return . runIdentity)
402--
424--
403-- /Pre-release/
425-- /Pre-release/
404generally :: Monad m => Fold Identity a b -> Fold m a b
426generally :: Monad m => Fold Identity a b -> Fold m a b
@@ -575,10 +597,12 @@ scanMany = scanWith True
575-- Filters
597-- Filters
576------------------------------------------------------------------------------
598------------------------------------------------------------------------------
577
599
578−-- | A filtering scan that deletes the first occurrence of the element in the
600+-- | Returns the latest element omitting the first occurrence that satisfies
579−-- stream that satisfies the given equality predicate.
601+-- the given equality predicate.
602+--
603+-- Example:
580--
604--
581−-- >>> input = Stream.unfold Unfold.fromList [1,3,3,5]
605+-- >>> input = Stream.fromList [1,3,3,5]
582-- >>> Stream.fold Fold.toList $ Stream.scanMaybe (Fold.deleteBy (==) 3) input
606-- >>> Stream.fold Fold.toList $ Stream.scanMaybe (Fold.deleteBy (==) 3) input
583-- [1,3,5]
607-- [1,3,5]
584--
608--
@@ -614,20 +638,19 @@ slide2 (Fold step1 initial1 extract1) = Fold step initial extract
614
638
615 extract (Tuple' _ s) = extract1 s
639 extract (Tuple' _ s) = extract1 s
616
640
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'.
619--
644--
620−-- To strip duplicate path separators:
645+-- Example, strip duplicate path separators:
621--
646--
622−-- >>> input = Stream.unfold Unfold.fromList "//a//b"
647+-- >>> input = Stream.fromList "//a//b"
623-- >>> f x y = x == '/' && y == '/'
648-- >>> f x y = x == '/' && y == '/'
624-- >>> Stream.fold Fold.toList $ Stream.scanMaybe (Fold.uniqBy f) input
649-- >>> Stream.fold Fold.toList $ Stream.scanMaybe (Fold.uniqBy f) input
625-- "/a/b"
650-- "/a/b"
626--
651--
627-- Space: @O(1)@
652-- Space: @O(1)@
628--
653--
629−-- See also: 'nubBy'.
630−--
631-- /Pre-release/
654-- /Pre-release/
632--
655--
633{-# INLINE uniqBy #-}
656{-# INLINE uniqBy #-}
@@ -641,7 +664,9 @@ uniqBy eq = rollingMap f
641 Nothing -> Just curr
664 Nothing -> Just curr
642 Just x -> if x `eq` curr then Nothing else Just curr
665 Just x -> if x `eq` curr then Nothing else Just curr
643
666
644−-- | Drop repeated elements that are adjacent to each other.
667+-- | See 'uniqBy'.
668+--
669+-- Definition:
645--
670--
646-- >>> uniq = Fold.uniqBy (==)
671-- >>> uniq = Fold.uniqBy (==)
647--
672--
@@ -676,43 +701,6 @@ repeated :: -- (Monad m, Eq a) =>
676 Fold m a (Maybe a)
701 Fold m a (Maybe a)
677repeated = error "Not implemented yet!"
702repeated = error "Not implemented yet!"
678
703
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−
716------------------------------------------------------------------------------
704------------------------------------------------------------------------------
717-- Left folds
705-- Left folds
718------------------------------------------------------------------------------
706------------------------------------------------------------------------------
@@ -722,28 +710,40 @@ nubInt = fmap (\(Tuple' _ x) -> x) $ foldl' step initial
722------------------------------------------------------------------------------
710------------------------------------------------------------------------------
723
711
724-- |
712-- |
713+-- Definitions:
714+--
725−-- > drainBy f = lmapM f drain
715+-- >>> drainMapM f = Fold.lmapM f Fold.drain
726−-- > drainBy = Fold.foldMapM (void . f)
716+-- >>> drainMapM f = Fold.foldMapM (void . f)
727--
717--
728-- Drain all input after passing it through a monadic function. This is the
718-- Drain all input after passing it through a monadic function. This is the
729-- dual of mapM_ on stream producers.
719-- dual of mapM_ on stream producers.
730--
720--
731−-- See also: 'Streamly.Prelude.mapM_'
721+{-# INLINE drainMapM #-}
732−--
722+drainMapM :: Monad m => (a -> m b) -> Fold m a ()
723+drainMapM f = lmapM f drain
724+
725+{-# DEPRECATED drainBy "Please use 'drainMapM' instead." #-}
733{-# INLINE drainBy #-}
726{-# INLINE drainBy #-}
734drainBy :: Monad m => (a -> m b) -> Fold m a ()
727drainBy :: Monad m => (a -> m b) -> Fold m a ()
735−drainBy f = lmapM f drain
728+drainBy = drainMapM
736
729
737−-- | Extract the last element of the input stream, if any.
730+-- | Returns the latest element of the input stream, if any.
738--
731--
732+-- >>> latest = Fold.foldl1' (\_ x -> x)
739−-- > last = fmap getLast $ Fold.foldMap (Last . Just)
733+-- >>> latest = fmap getLast $ Fold.foldMap (Last . Just)
740--
734--
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." #-}
741{-# INLINE last #-}
740{-# INLINE last #-}
742last :: Monad m => Fold m a (Maybe a)
741last :: Monad m => Fold m a (Maybe a)
743−last = foldl1' (\_ x -> x)
742+last = latest
744
743
745−-- | If the stream consists of one or more occurences of the same element
744+-- | Terminates with 'Nothing' as soon as it finds an element different than
746−-- then returns that element else terminates and returns 'Nothing'.
745+-- the previous one, returns 'the' element if the entire input consists of the
746+-- same element.
747--
747--
748{-# INLINE the #-}
748{-# INLINE the #-}
749the :: (Monad m, Eq a) => Fold m a (Maybe a)
749the :: (Monad m, Eq a) => Fold m a (Maybe a)
@@ -765,82 +765,27 @@ the = foldt' step initial id
765
765
766-- | Like 'length', except with a more general 'Num' return value
766-- | Like 'length', except with a more general 'Num' return value
767--
767--
768+-- Definition:
769+--
768−-- > genericLength = fmap getSum $ foldMap (Sum . const 1)
770+-- >>> lengthGeneric = fmap getSum $ Fold.foldMap (Sum . const 1)
771+-- >>> lengthGeneric = Fold.foldl' (\n _ -> n + 1) 0
769--
772--
770-- /Pre-release/
773-- /Pre-release/
771−{-# INLINE genericLength #-}
774+{-# INLINE lengthGeneric #-}
772−genericLength :: (Monad m, Num b) => Fold m a b
775+lengthGeneric :: (Monad m, Num b) => Fold m a b
773−genericLength = foldl' (\n _ -> n + 1) 0
776+lengthGeneric = foldl' (\n _ -> n + 1) 0
774
777
775-- | Determine the length of the input stream.
778-- | Determine the length of the input stream.
776--
779--
780+-- Definition:
781+--
782+-- >>> length = Fold.lengthGeneric
777−-- > length = fmap getSum $ Fold.foldMap (Sum . const 1)
783+-- >>> length = fmap getSum $ Fold.foldMap (Sum . const 1)
778--
784--
779{-# INLINE length #-}
785{-# INLINE length #-}
780length :: Monad m => Fold m a Int
786length :: Monad m => Fold m a Int
781−length = genericLength
787+length = lengthGeneric
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
808
788
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− -}
844
789
845-- | Determine the sum of all elements of a stream of numbers. Returns additive
790-- | Determine the sum of all elements of a stream of numbers. Returns additive
846-- identity (@0@) when the stream is empty. Note that this is not numerically
791-- identity (@0@) when the stream is empty. Note that this is not numerically
@@ -861,7 +806,9 @@ sum = FoldW.cumulative FoldW.sum
861-- multiplicative identity (@1@) when the stream is empty. The fold terminates
806-- multiplicative identity (@1@) when the stream is empty. The fold terminates
862-- when it encounters (@0@) in its input.
807-- when it encounters (@0@) in its input.
863--
808--
809+-- Same as the following but terminates on multiplication by @0@:
810+--
864−-- Compare with @Fold.foldMap Product@.
811+-- >>> product = fmap Data.Monoid.getProduct $ Fold.foldMap Data.Monoid.Product
865--
812--
866{-# INLINE product #-}
813{-# INLINE product #-}
867product :: (Monad m, Num a, Eq a) => Fold m a a
814product :: (Monad m, Num a, Eq a) => Fold m a a
@@ -892,14 +839,17 @@ maximumBy cmp = foldl1' max'
892 GT -> x
839 GT -> x
893 _ -> y
840 _ -> y
894
841
895−-- |
842+-- | Determine the maximum element in a stream.
896−-- @
897−-- maximum = Fold.maximumBy compare
898−-- @
843+--
844+-- Definitions:
899--
845--
900−-- Determine the maximum element in a stream.
846+-- >>> maximum = Fold.maximumBy compare
847+-- >>> maximum = Fold.foldl1' max
901--
848--
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
903--
853--
904{-# INLINE maximum #-}
854{-# INLINE maximum #-}
905maximum :: (Monad m, Ord a) => Fold m a (Maybe a)
855maximum :: (Monad m, Ord a) => Fold m a (Maybe a)
@@ -921,11 +871,15 @@ minimumBy cmp = foldl1' min'
921-- | Determine the minimum element in a stream using the supplied comparison
871-- | Determine the minimum element in a stream using the supplied comparison
922-- function.
872-- function.
923--
873--
924−-- @
874+-- Definitions:
875+--
925−-- minimum = 'minimumBy' compare
876+-- >>> minimum = Fold.minimumBy compare
926−-- @
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:
927--
881--
928−-- Compare with @Fold.foldMap Min@.
882+-- >>> maximum = fmap Data.Semigroup.getMin $ Fold.foldMap Data.Semigroup.Min
929--
883--
930{-# INLINE minimum #-}
884{-# INLINE minimum #-}
931minimum :: (Monad m, Ord a) => Fold m a (Maybe a)
885minimum :: (Monad m, Ord a) => Fold m a (Maybe a)
@@ -1011,7 +965,7 @@ defaultSalt = -2578643520546668380
1011
965
1012-- | Compute an 'Int' sized polynomial rolling hash of a stream.
966-- | Compute an 'Int' sized polynomial rolling hash of a stream.
1013--
967--
1014−-- > rollingHash = Fold.rollingHashWithSalt defaultSalt
968+-- >>> rollingHash = Fold.rollingHashWithSalt Fold.defaultSalt
1015--
969--
1016{-# INLINE rollingHash #-}
970{-# INLINE rollingHash #-}
1017rollingHash :: (Monad m, Enum a) => Fold m a Int64
971rollingHash :: (Monad m, Enum a) => Fold m a Int64
@@ -1020,7 +974,7 @@ rollingHash = rollingHashWithSalt defaultSalt
1020-- | Compute an 'Int' sized polynomial rolling hash of the first n elements of
974-- | Compute an 'Int' sized polynomial rolling hash of the first n elements of
1021-- a stream.
975-- a stream.
1022--
976--
1023−-- > rollingHashFirstN = Fold.take n Fold.rollingHash
977+-- >>> rollingHashFirstN n = Fold.take n Fold.rollingHash
1024--
978--
1025-- /Pre-release/
979-- /Pre-release/
1026{-# INLINE rollingHashFirstN #-}
980{-# INLINE rollingHashFirstN #-}
@@ -1053,6 +1007,9 @@ rollingMapM f = Fold step initial extract
1053
1007
1054 extract = return . snd
1008 extract = return . snd
1055
1009
1010+-- |
1011+-- >>> rollingMap f = Fold.rollingMapM (\x y -> return $ f x y)
1012+--
1056{-# INLINE rollingMap #-}
1013{-# INLINE rollingMap #-}
1057rollingMap :: Monad m => (Maybe a -> a -> b) -> Fold m a b
1014rollingMap :: Monad m => (Maybe a -> a -> b) -> Fold m a b
1058rollingMap f = rollingMapM (\x y -> return $ f x y)
1015rollingMap f = rollingMapM (\x y -> return $ f x y)
@@ -1061,26 +1018,31 @@ rollingMap f = rollingMapM (\x y -> return $ f x y)
1061-- Monoidal left folds
1018-- Monoidal left folds
1062------------------------------------------------------------------------------
1019------------------------------------------------------------------------------
1063
1020
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.
1065--
1023--
1066−-- >>> Stream.fold (Fold.sconcat 10) (fmap Data.Monoid.Sum $ Stream.enumerateFromTo 1 10)
1024+-- Definition:
1067−-- Sum {getSum = 65}
1068--
1025--
1069−-- @
1070−-- sconcat = Fold.foldl' (<>)
1026+-- >>> sconcat = Fold.foldl' (<>)
1071−-- @
1027+--
1028+-- >>> semigroups = fmap Data.Monoid.Sum $ Stream.enumerateFromTo 1 10
1029+-- >>> Stream.fold (Fold.sconcat 10) semigroups
1030+-- Sum {getSum = 65}
1072--
1031--
1073{-# INLINE sconcat #-}
1032{-# INLINE sconcat #-}
1074sconcat :: (Monad m, Semigroup a) => a -> Fold m a a
1033sconcat :: (Monad m, Semigroup a) => a -> Fold m a a
1075sconcat = foldl' (<>)
1034sconcat = foldl' (<>)
1076
1035
1077−-- | Fold an input stream consisting of monoidal elements using 'mappend'
1036+-- | Monoid concat. Fold an input stream consisting of monoidal elements using
1078−-- and 'mempty'.
1037+-- 'mappend' and 'mempty'.
1079--
1038--
1080−-- >>> Stream.fold Fold.mconcat (fmap Data.Monoid.Sum $ Stream.enumerateFromTo 1 10)
1039+-- Definition:
1081−-- Sum {getSum = 55}
1082--
1040--
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}
1084--
1046--
1085{-# INLINE mconcat #-}
1047{-# INLINE mconcat #-}
1086mconcat ::
1048mconcat ::
@@ -1089,26 +1051,31 @@ mconcat ::
1089mconcat = sconcat mempty
1051mconcat = sconcat mempty
1090
1052
1091-- |
1053-- |
1054+-- Definition:
1055+--
1092−-- > foldMap f = Fold.lmap f Fold.mconcat
1056+-- >>> foldMap f = Fold.lmap f Fold.mconcat
1093--
1057--
1094-- Make a fold from a pure function that folds the output of the function
1058-- Make a fold from a pure function that folds the output of the function
1095-- using 'mappend' and 'mempty'.
1059-- using 'mappend' and 'mempty'.
1096--
1060--
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
1098-- Sum {getSum = 55}
1063-- Sum {getSum = 55}
1099--
1064--
1100{-# INLINE foldMap #-}
1065{-# INLINE foldMap #-}
1101−foldMap :: (Monad m, Monoid b
1066+foldMap :: (Monad m, Monoid b) => (a -> b) -> Fold m a b
1102− ) => (a -> b) -> Fold m a b
1103foldMap f = lmap f mconcat
1067foldMap f = lmap f mconcat
1104
1068
1105-- |
1069-- |
1070+-- Definition:
1071+--
1106−-- > foldMapM f = Fold.lmapM f Fold.mconcat
1072+-- >>> foldMapM f = Fold.lmapM f Fold.mconcat
1107--
1073--
1108-- Make a fold from a monadic function that folds the output of the function
1074-- Make a fold from a monadic function that folds the output of the function
1109-- using 'mappend' and 'mempty'.
1075-- using 'mappend' and 'mempty'.
1110--
1076--
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
1112-- Sum {getSum = 55}
1079-- Sum {getSum = 55}
1113--
1080--
1114{-# INLINE foldMapM #-}
1081{-# INLINE foldMapM #-}
@@ -1131,7 +1098,9 @@ foldMapM act = foldlM' step (pure mempty)
1131
1098
1132-- | Buffers the input stream to a list in the reverse order of the input.
1099-- | Buffers the input stream to a list in the reverse order of the input.
1133--
1100--
1101+-- Definition:
1102+--
1134−-- > toListRev = Fold.foldl' (flip (:)) []
1103+-- >>> toListRev = Fold.foldl' (flip (:)) []
1135--
1104--
1136-- /Warning!/ working on large lists accumulated as buffers in memory could be
1105-- /Warning!/ working on large lists accumulated as buffers in memory could be
1137-- very inefficient, consider using "Streamly.Array" instead.
1106-- very inefficient, consider using "Streamly.Array" instead.
@@ -1149,7 +1118,9 @@ toListRev = foldl' (flip (:)) []
1149-- | A fold that drains the first n elements of its input, running the effects
1118-- | A fold that drains the first n elements of its input, running the effects
1150-- and discarding the results.
1119-- and discarding the results.
1151--
1120--
1121+-- Definition:
1122+--
1152−-- > drainN n = Fold.take n Fold.drain
1123+-- >>> drainN n = Fold.take n Fold.drain
1153--
1124--
1154-- /Pre-release/
1125-- /Pre-release/
1155{-# INLINE drainN #-}
1126{-# INLINE drainN #-}
@@ -1163,9 +1134,9 @@ drainN n = take n drain
1163-- | Like 'index', except with a more general 'Integral' argument
1134-- | Like 'index', except with a more general 'Integral' argument
1164--
1135--
1165-- /Pre-release/
1136-- /Pre-release/
1166−{-# INLINE genericIndex #-}
1137+{-# INLINE indexGeneric #-}
1167−genericIndex :: (Integral i, Monad m) => i -> Fold m a (Maybe a)
1138+indexGeneric :: (Integral i, Monad m) => i -> Fold m a (Maybe a)
1168−genericIndex i = foldt' step (Partial 0) (const Nothing)
1139+indexGeneric i = foldt' step (Partial 0) (const Nothing)
1169
1140
1170 where
1141 where
1171
1142
@@ -1174,15 +1145,18 @@ genericIndex i = foldt' step (Partial 0) (const Nothing)
1174 then Done $ Just a
1145 then Done $ Just a
1175 else Partial (j + 1)
1146 else Partial (j + 1)
1176
1147
1177−-- | Lookup the element at the given index.
1148+-- | Return the element at the given index.
1178--
1149--
1179−-- See also: 'Streamly.Prelude.!!'
1150+-- Definition:
1151+--
1152+-- >>> index = Fold.indexGeneric
1180--
1153--
1181{-# INLINE index #-}
1154{-# INLINE index #-}
1182index :: Monad m => Int -> Fold m a (Maybe a)
1155index :: Monad m => Int -> Fold m a (Maybe a)
1183−index = genericIndex
1156+index = indexGeneric
1184
1157
1158+-- | Consume a single input and transform it using the supplied 'Maybe'
1185−-- | Map a 'Maybe' returning function on the next element in the stream.
1159+-- returning function.
1186--
1160--
1187-- /Pre-release/
1161-- /Pre-release/
1188--
1162--
@@ -1190,16 +1164,24 @@ index = genericIndex
1190maybe :: Monad m => (a -> Maybe b) -> Fold m a (Maybe b)
1164maybe :: Monad m => (a -> Maybe b) -> Fold m a (Maybe b)
1191maybe f = foldt' (const (Done . f)) (Partial Nothing) id
1165maybe f = foldt' (const (Done . f)) (Partial Nothing) id
1192
1166
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)
1194--
1173--
1195-- /Pre-release/
1174-- /Pre-release/
1196{-# INLINE satisfy #-}
1175{-# INLINE satisfy #-}
1197satisfy :: Monad m => (a -> Bool) -> Fold m a (Maybe a)
1176satisfy :: Monad m => (a -> Bool) -> Fold m a (Maybe a)
1177+satisfy f = maybe (\a -> if f a then Just a else Nothing)
1178+{-
1198satisfy f = Fold step (return $ Partial ()) (const (return Nothing))
1179satisfy f = Fold step (return $ Partial ()) (const (return Nothing))
1199
1180
1200 where
1181 where
1201
1182
1202 step () a = return $ Done $ if f a then Just a else Nothing
1183 step () a = return $ Done $ if f a then Just a else Nothing
1184+-}
1203
1185
1204-- Naming notes:
1186-- Naming notes:
1205--
1187--
@@ -1228,6 +1210,12 @@ satisfy f = Fold step (return $ Partial ()) (const (return Nothing))
1228
1210
1229-- | Take one element from the stream and stop.
1211-- | Take one element from the stream and stop.
1230--
1212--
1213+-- Definition:
1214+--
1215+-- >>> one = Fold.maybe Just
1216+--
1217+-- This is similar to the stream 'Stream.uncons' operation.
1218+--
1231{-# INLINE one #-}
1219{-# INLINE one #-}
1232one :: Monad m => Fold m a (Maybe a)
1220one :: Monad m => Fold m a (Maybe a)
1233one = maybe Just
1221one = maybe Just
@@ -1266,7 +1254,9 @@ find p = findM (return . p)
1266-- | In a stream of (key-value) pairs @(a, b)@, return the value @b@ of the
1254-- | In a stream of (key-value) pairs @(a, b)@, return the value @b@ of the
1267-- first pair where the key equals the given value @a@.
1255-- first pair where the key equals the given value @a@.
1268--
1256--
1257+-- Definition:
1258+--
1269−-- > lookup = snd <$> Fold.find ((==) . fst)
1259+-- >>> lookup x = fmap snd <$> Fold.find ((== x) . fst)
1270--
1260--
1271{-# INLINE lookup #-}
1261{-# INLINE lookup #-}
1272lookup :: (Eq a, Monad m) => a -> Fold m (a,b) (Maybe b)
1262lookup :: (Eq a, Monad m) => a -> Fold m (a,b) (Maybe b)
@@ -1292,10 +1282,8 @@ findIndex predicate = foldt' step (Partial 0) (const Nothing)
1292 then Done $ Just i
1282 then Done $ Just i
1293 else Partial (i + 1)
1283 else Partial (i + 1)
1294
1284
1295−-- XXX If we have a Continue contructor we won't need a Maybe type for the
1285+-- | Returns the index of the latest element if the element satisfies the given
1296−-- scan. The fold will have the ability to skip producing output.
1286+-- predicate.
1297−--
1298−-- | Returns all indices that satisfy the given predicate.
1299--
1287--
1300{-# INLINE findIndices #-}
1288{-# INLINE findIndices #-}
1301findIndices :: Monad m => (a -> Bool) -> Fold m a (Maybe Int)
1289findIndices :: Monad m => (a -> Bool) -> Fold m a (Maybe Int)
@@ -1310,8 +1298,10 @@ findIndices predicate =
1310 then Right (either id id i + 1)
1298 then Right (either id id i + 1)
1311 else Left (either id id i + 1)
1299 else Left (either id id i + 1)
1312
1300
1313−-- | Find all the indices where the value of the element in the stream is equal
1301+-- | Returns the index of the latest element if the element matches the given
1314−-- to the given value.
1302+-- value.
1303+--
1304+-- Definition:
1315--
1305--
1316-- >>> elemIndices a = Fold.findIndices (== a)
1306-- >>> elemIndices a = Fold.findIndices (== a)
1317--
1307--
@@ -1321,31 +1311,46 @@ elemIndices a = findIndices (== a)
1321
1311
1322-- | Returns the first index where a given value is found in the stream.
1312-- | Returns the first index where a given value is found in the stream.
1323--
1313--
1314+-- Definition:
1315+--
1324−-- > elemIndex a = Fold.findIndex (== a)
1316+-- >>> elemIndex a = Fold.findIndex (== a)
1325--
1317--
1326{-# INLINE elemIndex #-}
1318{-# INLINE elemIndex #-}
1327elemIndex :: (Eq a, Monad m) => a -> Fold m a (Maybe Int)
1319elemIndex :: (Eq a, Monad m) => a -> Fold m a (Maybe Int)
1328−elemIndex a = findIndex (a ==)
1320+elemIndex a = findIndex (== a)
1329
1321
1330------------------------------------------------------------------------------
1322------------------------------------------------------------------------------
1331-- To Boolean
1323-- To Boolean
1332------------------------------------------------------------------------------
1324------------------------------------------------------------------------------
1333
1325
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
1334−-- | Return 'True' if the input stream is empty.
1330+-- other words, test if the input is empty or not.
1335--
1331--
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+--
1336−-- > null = fmap isJust Fold.one
1337+-- >>> null = fmap isJust Fold.one
1337--
1338--
1338{-# INLINE null #-}
1339{-# INLINE null #-}
1339null :: Monad m => Fold m a Bool
1340null :: Monad m => Fold m a Bool
1340null = foldt' (\() _ -> Done False) (Partial ()) (const True)
1341null = foldt' (\() _ -> Done False) (Partial ()) (const True)
1341
1342
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:
1343--
1350--
1344-- >>> Stream.fold (Fold.any (== 0)) $ Stream.fromList [1,0,1]
1351-- >>> Stream.fold (Fold.any (== 0)) $ Stream.fromList [1,0,1]
1345-- True
1352-- True
1346--
1353--
1347−-- > any p = Fold.lmap p Fold.or
1348−--
1349{-# INLINE any #-}
1354{-# INLINE any #-}
1350any :: Monad m => (a -> Bool) -> Fold m a Bool
1355any :: Monad m => (a -> Bool) -> Fold m a Bool
1351any predicate = foldt' step initial id
1356any predicate = foldt' step initial id
@@ -1361,19 +1366,25 @@ any predicate = foldt' step initial id
1361
1366
1362-- | Return 'True' if the given element is present in the stream.
1367-- | Return 'True' if the given element is present in the stream.
1363--
1368--
1369+-- Definition:
1370+--
1364−-- > elem a = Fold.any (== a)
1371+-- >>> elem a = Fold.any (== a)
1365--
1372--
1366{-# INLINE elem #-}
1373{-# INLINE elem #-}
1367elem :: (Eq a, Monad m) => a -> Fold m a Bool
1374elem :: (Eq a, Monad m) => a -> Fold m a Bool
1368−elem a = any (a ==)
1375+elem a = any (== a)
1369
1376
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:
1371--
1384--
1372-- >>> Stream.fold (Fold.all (== 0)) $ Stream.fromList [1,0,1]
1385-- >>> Stream.fold (Fold.all (== 0)) $ Stream.fromList [1,0,1]
1373-- False
1386-- False
1374--
1387--
1375−-- > all p = Fold.lmap p Fold.and
1376−--
1377{-# INLINE all #-}
1388{-# INLINE all #-}
1378all :: Monad m => (a -> Bool) -> Fold m a Bool
1389all :: Monad m => (a -> Bool) -> Fold m a Bool
1379all predicate = foldt' step initial id
1390all predicate = foldt' step initial id
@@ -1389,15 +1400,19 @@ all predicate = foldt' step initial id
1389
1400
1390-- | Returns 'True' if the given element is not present in the stream.
1401-- | Returns 'True' if the given element is not present in the stream.
1391--
1402--
1403+-- Definition:
1404+--
1392−-- > notElem a = Fold.all (/= a)
1405+-- >>> notElem a = Fold.all (/= a)
1393--
1406--
1394{-# INLINE notElem #-}
1407{-# INLINE notElem #-}
1395notElem :: (Eq a, Monad m) => a -> Fold m a Bool
1408notElem :: (Eq a, Monad m) => a -> Fold m a Bool
1396−notElem a = all (a /=)
1409+notElem a = all (/= a)
1397
1410
1398-- | Returns 'True' if all elements are 'True', 'False' otherwise
1411-- | Returns 'True' if all elements are 'True', 'False' otherwise
1399--
1412--
1413+-- Definition:
1414+--
1400−-- > and = Fold.all (== True)
1415+-- >>> and = Fold.all (== True)
1401--
1416--
1402{-# INLINE and #-}
1417{-# INLINE and #-}
1403and :: Monad m => Fold m Bool Bool
1418and :: Monad m => Fold m Bool Bool
@@ -1405,7 +1420,9 @@ and = all (== True)
1405
1420
1406-- | Returns 'True' if any element is 'True', 'False' otherwise
1421-- | Returns 'True' if any element is 'True', 'False' otherwise
1407--
1422--
1423+-- Definition:
1424+--
1408−-- > or = Fold.any (== True)
1425+-- >>> or = Fold.any (== True)
1409--
1426--
1410{-# INLINE or #-}
1427{-# INLINE or #-}
1411or :: Monad m => Fold m Bool Bool
1428or :: Monad m => Fold m Bool Bool
@@ -1531,6 +1548,9 @@ droppingWhileM p = Fold step initial (return . toMaybe)
1531 else Just' a
1548 else Just' a
1532 step _ a = return $ Partial $ Just' a
1549 step _ a = return $ Partial $ Just' a
1533
1550
1551+-- |
1552+-- >>> droppingWhile p = Fold.droppingWhileM (return . p)
1553+--
1534{-# INLINE droppingWhile #-}
1554{-# INLINE droppingWhile #-}
1535droppingWhile :: Monad m => (a -> Bool) -> Fold m a (Maybe a)
1555droppingWhile :: Monad m => (a -> Bool) -> Fold m a (Maybe a)
1536droppingWhile p = droppingWhileM (return . p)
1556droppingWhile p = droppingWhileM (return . p)
@@ -1539,20 +1559,20 @@ droppingWhile p = droppingWhileM (return . p)
1539-- S.splitOn in favor of the fold.
1559-- S.splitOn in favor of the fold.
1540--
1560--
1541-- XXX Use Fold.many instead once it is fixed.
1561-- XXX Use Fold.many instead once it is fixed.
1542−--
1562+-- > Stream.splitOnSuffix p f = Stream.foldMany (Fold.takeEndBy_ p f)
1563+
1543-- | Like 'takeEndBy' but drops the element on which the predicate succeeds.
1564-- | Like 'takeEndBy' but drops the element on which the predicate succeeds.
1544--
1565--
1566+-- Example:
1567+--
1545−-- >>> Stream.fold (Fold.takeEndBy_ (== '\n') Fold.toList) $ Stream.fromList "hello\nthere\n"
1568+-- >>> input = Stream.fromList "hello\nthere\n"
1569+-- >>> line = Fold.takeEndBy_ (== '\n') Fold.toList
1570+-- >>> Stream.fold line input
1546-- "hello"
1571-- "hello"
1547--
1572--
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
1549-- ["hello","there"]
1574-- ["hello","there"]
1550--
1575--
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−--
1556{-# INLINE takeEndBy_ #-}
1576{-# INLINE takeEndBy_ #-}
1557takeEndBy_ :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a b
1577takeEndBy_ :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a b
1558-- takeEndBy_ predicate = scanMaybe (takingEndBy_ predicate)
1578-- takeEndBy_ predicate = scanMaybe (takingEndBy_ predicate)
@@ -1566,20 +1586,22 @@ takeEndBy_ predicate (Fold fstep finitial fextract) =
1566 then fstep s a
1586 then fstep s a
1567 else Done <$> fextract s
1587 else Done <$> fextract s
1568
1588
1589+-- Note:
1590+-- > Stream.splitWithSuffix p f = Stream.foldMany (Fold.takeEndBy p f)
1591+
1569-- | Take the input, stop when the predicate succeeds taking the succeeding
1592-- | Take the input, stop when the predicate succeeds taking the succeeding
1570-- element as well.
1593-- element as well.
1571--
1594--
1595+-- Example:
1596+--
1572−-- >>> Stream.fold (Fold.takeEndBy (== '\n') Fold.toList) $ Stream.fromList "hello\nthere\n"
1597+-- >>> input = Stream.fromList "hello\nthere\n"
1598+-- >>> line = Fold.takeEndBy (== '\n') Fold.toList
1599+-- >>> Stream.fold line input
1573-- "hello\n"
1600-- "hello\n"
1574--
1601--
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
1576-- ["hello\n","there\n"]
1603-- ["hello\n","there\n"]
1577--
1604--
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−--
1583{-# INLINE takeEndBy #-}
1605{-# INLINE takeEndBy #-}
1584takeEndBy :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a b
1606takeEndBy :: Monad m => (a -> Bool) -> Fold m a b -> Fold m a b
1585-- takeEndBy predicate = scanMaybe (takingEndBy predicate)
1607-- takeEndBy predicate = scanMaybe (takingEndBy predicate)
@@ -1924,10 +1946,16 @@ takeEndBySeq_ patArr (Fold fstep finitial fextract) =
1924-- ---stream m a---| |---m (b,c)
1946-- ---stream m a---| |---m (b,c)
1925-- |-------Fold m a c--------|
1947-- |-------Fold m a c--------|
1926-- @
1948-- @
1927−-- >>> Stream.fold (Fold.tee Fold.sum Fold.length) (Stream.enumerateFromTo 1.0 100.0)
1928−-- (5050.0,100)
1929--
1949--
1950+-- Definition:
1951+--
1930−-- > tee = teeWith (,)
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)
1931--
1959--
1932{-# INLINE tee #-}
1960{-# INLINE tee #-}
1933tee :: Monad m => Fold m a b -> Fold m a c -> Fold m a (b,c)
1961tee :: Monad m => Fold m a b -> Fold m a c -> Fold m a (b,c)
@@ -1992,16 +2020,15 @@ partitionByMUsing t f fld1 fld2 =
1992-- |-------Fold c y--------|
2020-- |-------Fold c y--------|
1993-- @
2021-- @
1994--
2022--
1995−-- Send input to either fold randomly:
2023+-- Example, send input to either fold randomly:
1996--
2024--
1997−-- @
1998−-- > import System.Random (randomIO)
2025+-- >>> import System.Random (randomIO)
1999−-- > randomly a = randomIO >>= \\x -> return $ if x then Left a else Right a
2026+-- >>> randomly a = randomIO >>= \x -> return $ if x then Left a else Right a
2027+-- >>> f = Fold.partitionByM randomly Fold.length Fold.length
2000−-- > Stream.fold (Fold.partitionByM randomly Fold.length Fold.length) (Stream.enumerateFromTo 1 100)
2028+-- >>> Stream.fold f (Stream.enumerateFromTo 1 100)
2001−-- (59,41)
2029+-- ...
2002−-- @
2003--
2030--
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:
2005--
2032--
2006-- >>> :{
2033-- >>> :{
2007-- proportionately m n = do
2034-- proportionately m n = do
@@ -2014,8 +2041,9 @@ partitionByMUsing t f fld1 fld2 =
2014--
2041--
2015-- >>> :{
2042-- >>> :{
2016-- main = do
2043-- main = do
2017−-- f <- proportionately 2 1
2044+-- g <- proportionately 2 1
2045+-- let f = Fold.partitionByM g Fold.length Fold.length
2018−-- r <- Stream.fold (Fold.partitionByM f Fold.length Fold.length) (Stream.enumerateFromTo (1 :: Int) 100)
2046+-- r <- Stream.fold f (Stream.enumerateFromTo (1 :: Int) 100)
2019-- print r
2047-- print r
2020-- :}
2048-- :}
2021--
2049--
@@ -2040,7 +2068,6 @@ partitionByM = partitionByMUsing teeWith
2040
2068
2041-- | Similar to 'partitionByM' but terminates when the first fold terminates.
2069-- | Similar to 'partitionByM' but terminates when the first fold terminates.
2042--
2070--
2043−--
2044{-# INLINE partitionByFstM #-}
2071{-# INLINE partitionByFstM #-}
2045partitionByFstM :: Monad m
2072partitionByFstM :: Monad m
2046 => (a -> m (Either b c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)
2073 => (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
2048
2075
2049-- | Similar to 'partitionByM' but terminates when any fold terminates.
2076-- | Similar to 'partitionByM' but terminates when any fold terminates.
2050--
2077--
2051−--
2052{-# INLINE partitionByMinM #-}
2078{-# INLINE partitionByMinM #-}
2053partitionByMinM :: Monad m =>
2079partitionByMinM :: Monad m =>
2054 (a -> m (Either b c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)
2080 (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
2057-- Note: we could use (a -> Bool) instead of (a -> Either b c), but the latter
2083-- Note: we could use (a -> Bool) instead of (a -> Either b c), but the latter
2058-- makes the signature clearer as to which case belongs to which fold.
2084-- makes the signature clearer as to which case belongs to which fold.
2059-- XXX need to check the performance in both cases.
2085-- XXX need to check the performance in both cases.
2086+
2060-- | Same as 'partitionByM' but with a pure partition function.
2087-- | Same as 'partitionByM' but with a pure partition function.
2061--
2088--
2062−-- Count even and odd numbers in a stream:
2089+-- Example, count even and odd numbers in a stream:
2063--
2090--
2064-- >>> :{
2091-- >>> :{
2065-- let f = Fold.partitionBy (\n -> if even n then Left n else Right n)
2092-- let f = Fold.partitionBy (\n -> if even n then Left n else Right n)
@@ -2079,7 +2106,9 @@ partitionBy f = partitionByM (return . f)
2079-- and routes the 'Left' values to the first fold and 'Right' values to the
2106-- and routes the 'Left' values to the first fold and 'Right' values to the
2080-- second fold.
2107-- second fold.
2081--
2108--
2109+-- Definition:
2110+--
2082−-- > partition = partitionBy id
2111+-- >>> partition = Fold.partitionBy id
2083--
2112--
2084{-# INLINE partition #-}
2113{-# INLINE partition #-}
2085partition :: Monad m
2114partition :: Monad m
@@ -2094,412 +2123,6 @@ partition = partitionBy id
2094-- partitionN fs = Fold step begin done
2123-- partitionN fs = Fold step begin done
2095-}
2124-}
2096
2125
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−
2503------------------------------------------------------------------------------
2126------------------------------------------------------------------------------
2504-- Unzipping
2127-- Unzipping
2505------------------------------------------------------------------------------
2128------------------------------------------------------------------------------
@@ -2522,7 +2145,9 @@ unzipWithMUsing t f fld1 fld2 =
2522
2145
2523-- | Like 'unzipWith' but with a monadic splitter function.
2146-- | Like 'unzipWith' but with a monadic splitter function.
2524--
2147--
2148+-- Definition:
2149+--
2525−-- @unzipWithM k f1 f2 = lmapM k (unzip f1 f2)@
2150+-- >>> unzipWithM k f1 f2 = Fold.lmapM k (Fold.unzip f1 f2)
2526--
2151--
2527-- /Pre-release/
2152-- /Pre-release/
2528{-# INLINE unzipWithM #-}
2153{-# INLINE unzipWithM #-}
@@ -2532,7 +2157,6 @@ unzipWithM = unzipWithMUsing teeWith
2532
2157
2533-- | Similar to 'unzipWithM' but terminates when the first fold terminates.
2158-- | Similar to 'unzipWithM' but terminates when the first fold terminates.
2534--
2159--
2535−--
2536{-# INLINE unzipWithFstM #-}
2160{-# INLINE unzipWithFstM #-}
2537unzipWithFstM :: Monad m =>
2161unzipWithFstM :: Monad m =>
2538 (a -> m (b, c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)
2162 (a -> m (b, c)) -> Fold m b x -> Fold m c y -> Fold m a (x, y)
@@ -2540,7 +2164,6 @@ unzipWithFstM = unzipWithMUsing teeWithFst
2540
2164
2541-- | Similar to 'unzipWithM' but terminates when any fold terminates.
2165-- | Similar to 'unzipWithM' but terminates when any fold terminates.
2542--
2166--
2543−--
2544{-# INLINE unzipWithMinM #-}
2167{-# INLINE unzipWithMinM #-}
2545unzipWithMinM :: Monad m =>
2168unzipWithMinM :: Monad m =>
2546 (a -> m (b,c)) -> Fold m b x -> Fold m c y -> Fold m a (x,y)
2169 (a -> m (b,c)) -> Fold m b x -> Fold m c y -> Fold m a (x,y)
@@ -2549,7 +2172,10 @@ unzipWithMinM = unzipWithMUsing teeWithMin
2549-- | Split elements in the input stream into two parts using a pure splitter
2172-- | Split elements in the input stream into two parts using a pure splitter
2550-- function, direct each part to a different fold and zip the results.
2173-- function, direct each part to a different fold and zip the results.
2551--
2174--
2175+-- Definitions:
2176+--
2177+-- >>> unzipWith f = Fold.unzipWithM (return . f)
2552−-- @unzipWith f fld1 fld2 = Fold.lmap f (Fold.unzip fld1 fld2)@
2178+-- >>> unzipWith f fld1 fld2 = Fold.lmap f (Fold.unzip fld1 fld2)
2553--
2179--
2554-- This fold terminates when both the input folds terminate.
2180-- This fold terminates when both the input folds terminate.
2555--
2181--
@@ -2570,7 +2196,9 @@ unzipWith f = unzipWithM (return . f)
2570--
2196--
2571-- @
2197-- @
2572--
2198--
2199+-- Definition:
2200+--
2573−-- > unzip = Fold.unzipWith id
2201+-- >>> unzip = Fold.unzipWith id
2574--
2202--
2575-- This is the consumer side dual of the producer side 'zip' operation.
2203-- This is the consumer side dual of the producer side 'zip' operation.
2576--
2204--
@@ -2588,22 +2216,27 @@ unzip = unzipWith id
2588-- handled in parsers using the Continue facility. See zipWithM in the Parser
2216-- handled in parsers using the Continue facility. See zipWithM in the Parser
2589-- module.
2217-- module.
2590--
2218--
2219+-- cmpBy, eqBy, isPrefixOf, isSubsequenceOf etc can be implemented using
2220+-- zipStream.
2221+
2591-- | Zip a stream with the input of a fold using the supplied function.
2222-- | Zip a stream with the input of a fold using the supplied function.
2592--
2223--
2593-- /Unimplemented/
2224-- /Unimplemented/
2594--
2225--
2595−{-# INLINE zipWithM #-}
2226+{-# INLINE zipStreamWithM #-}
2596−zipWithM :: -- Monad m =>
2227+zipStreamWithM :: -- Monad m =>
2597− (a -> b -> m c) -> t m a -> Fold m c x -> Fold m b x
2228+ (a -> b -> m c) -> Stream m a -> Fold m c x -> Fold m b x
2598−zipWithM = undefined
2229+zipStreamWithM = undefined
2599
2230
2600-- | Zip a stream with the input of a fold.
2231-- | Zip a stream with the input of a fold.
2601--
2232--
2233+-- >>> zip = Fold.zipStreamWithM (curry return)
2234+--
2602-- /Unimplemented/
2235-- /Unimplemented/
2603--
2236--
2604−{-# INLINE zip #-}
2237+{-# INLINE zipStream #-}
2605−zip :: Monad m => t m a -> Fold m (a, b) x -> Fold m b x
2238+zipStream :: Monad m => Stream m a -> Fold m (a, b) x -> Fold m b x
2606−zip = zipWithM (curry return)
2239+zipStream = zipStreamWithM (curry return)
2607
2240
2608-- | Pair each element of a fold input with its index, starting from index 0.
2241-- | Pair each element of a fold input with its index, starting from index 0.
2609--
2242--
@@ -2618,16 +2251,24 @@ indexingWith i f = fmap toMaybe $ foldl' step initial
2618 step Nothing' a = Just' (i, a)
2251 step Nothing' a = Just' (i, a)
2619 step (Just' (n, _)) a = Just' (f n, a)
2252 step (Just' (n, _)) a = Just' (f n, a)
2620
2253
2254+-- |
2255+-- >>> indexing = Fold.indexingWith 0 (+ 1)
2256+--
2621{-# INLINE indexing #-}
2257{-# INLINE indexing #-}
2622indexing :: Monad m => Fold m a (Maybe (Int, a))
2258indexing :: Monad m => Fold m a (Maybe (Int, a))
2623indexing = indexingWith 0 (+ 1)
2259indexing = indexingWith 0 (+ 1)
2624
2260
2261+-- |
2262+-- >>> indexingRev n = Fold.indexingWith n (subtract 1)
2263+--
2625{-# INLINE indexingRev #-}
2264{-# INLINE indexingRev #-}
2626indexingRev :: Monad m => Int -> Fold m a (Maybe (Int, a))
2265indexingRev :: Monad m => Int -> Fold m a (Maybe (Int, a))
2627indexingRev n = indexingWith n (subtract 1)
2266indexingRev n = indexingWith n (subtract 1)
2628
2267
2629-- | Pair each element of a fold input with its index, starting from index 0.
2268-- | Pair each element of a fold input with its index, starting from index 0.
2630--
2269--
2270+-- >>> indexed = Fold.scanMaybe Fold.indexing
2271+--
2631{-# INLINE indexed #-}
2272{-# INLINE indexed #-}
2632indexed :: Monad m => Fold m (Int, a) b -> Fold m a b
2273indexed :: Monad m => Fold m (Int, a) b -> Fold m a b
2633indexed = scanMaybe indexing
2274indexed = scanMaybe indexing
@@ -2636,8 +2277,9 @@ indexed = scanMaybe indexing
2636-- additional state input @(s, a) -> b@. Convenient to filter with an
2277-- additional state input @(s, a) -> b@. Convenient to filter with an
2637-- addiitonal index or time input.
2278-- addiitonal index or time input.
2638--
2279--
2280+-- >>> filterWithIndex = Fold.with Fold.indexed Fold.filter
2281+--
2639-- @
2282-- @
2640−-- filterWithIndex = with indexed filter
2641-- filterWithAbsTime = with timestamped filter
2283-- filterWithAbsTime = with timestamped filter
2642-- filterWithRelTime = with timeIndexed filter
2284-- filterWithRelTime = with timeIndexed filter
2643-- @
2285-- @
@@ -2756,12 +2398,14 @@ unfoldMany (Unfold ustep inject) (Fold fstep initial extract) =
2756 {-# INLINE_LATE consume #-}
2398 {-# INLINE_LATE consume #-}
2757 consume s a = inject a >>= produce s
2399 consume s a = inject a >>= produce s
2758
2400
2401+-- | Get the bottom most @n@ elements using the supplied comparison function.
2402+--
2759−{-# INLINE topBy #-}
2403+{-# INLINE bottomBy #-}
2760−topBy :: (MonadIO m, Unbox a) =>
2404+bottomBy :: (MonadIO m, Unbox a) =>
2761 (a -> a -> Ordering)
2405 (a -> a -> Ordering)
2762 -> Int
2406 -> Int
2763 -> Fold m a (MA.Array a)
2407 -> Fold m a (MA.Array a)
2764−topBy cmp n = Fold step initial extract
2408+bottomBy cmp n = Fold step initial extract
2765
2409
2766 where
2410 where
2767
2411
@@ -2790,6 +2434,10 @@ topBy cmp n = Fold step initial extract
2790
2434
2791-- | Fold the input stream to top n elements.
2435-- | Fold the input stream to top n elements.
2792--
2436--
2437+-- Definition:
2438+--
2439+-- >>> top = Fold.bottomBy $ flip compare
2440+--
2793-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]
2441-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]
2794-- >>> Stream.fold (Fold.top 3) stream >>= MA.toList
2442-- >>> Stream.fold (Fold.top 3) stream >>= MA.toList
2795-- [17,11,9]
2443-- [17,11,9]
@@ -2797,10 +2445,14 @@ topBy cmp n = Fold step initial extract
2797-- /Pre-release/
2445-- /Pre-release/
2798{-# INLINE top #-}
2446{-# INLINE top #-}
2799top :: (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (MA.Array a)
2447top :: (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (MA.Array a)
2800−top = topBy $ flip compare
2448+top = bottomBy $ flip compare
2801
2449
2802-- | Fold the input stream to bottom n elements.
2450-- | Fold the input stream to bottom n elements.
2803--
2451--
2452+-- Definition:
2453+--
2454+-- >>> bottom = Fold.bottomBy compare
2455+--
2804-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]
2456-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]
2805-- >>> Stream.fold (Fold.bottom 3) stream >>= MA.toList
2457-- >>> Stream.fold (Fold.bottom 3) stream >>= MA.toList
2806-- [1,2,3]
2458-- [1,2,3]
@@ -2808,4 +2460,4 @@ top = topBy $ flip compare
2808-- /Pre-release/
2460-- /Pre-release/
2809{-# INLINE bottom #-}
2461{-# INLINE bottom #-}
2810bottom :: (MonadIO m, Unbox a, Ord a) => Int -> Fold m a (MA.Array a)
2462bottom :: (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 @@
322-- can be expressed using 'mconcat' and a suitable 'Monoid'. Instead of
322-- can be expressed using 'mconcat' and a suitable 'Monoid'. Instead of
323-- writing folds we can write Monoids and turn them into folds.
323-- writing folds we can write Monoids and turn them into folds.
324--
324--
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−--
335module Streamly.Internal.Data.Fold.Type
325module Streamly.Internal.Data.Fold.Type
336 (
326 (
327+ -- * Imports
328+ -- $setup
329+
337 -- * Types
330 -- * Types
338 Step (..)
331 Step (..)
339 , Fold (..)
332 , Fold (..)
@@ -344,8 +337,8 @@ module Streamly.Internal.Data.Fold.Type
344 , foldl1'
337 , foldl1'
345 , foldt'
338 , foldt'
346 , foldtM'
339 , foldtM'
347− , foldr
340+ , foldr'
348− , foldrM
341+ , foldrM'
349
342
350 -- * Folds
343 -- * Folds
351 , fromPure
344 , fromPure
@@ -395,6 +388,7 @@ module Streamly.Internal.Data.Fold.Type
395
388
396 -- ** Nested Application
389 -- ** Nested Application
397 , concatMap
390 , concatMap
391+ , duplicate
398 , refold
392 , refold
399
393
400 -- ** Parallel Distribution
394 -- ** Parallel Distribution
@@ -407,11 +401,17 @@ module Streamly.Internal.Data.Fold.Type
407 , longest
401 , longest
408
402
409 -- * Running A Fold
403 -- * Running A Fold
410− , initialize
404+ , extractM
405+ , reduce
411 , snoc
406 , snoc
412− , duplicate
407+ , snocM
413− , finish
408+ , snocl
414− , isDone
409+ , snoclM
410+ , close
411+ , isClosed
412+
413+ -- * Deprecated
414+ , foldr
415 )
415 )
416where
416where
417
417
@@ -434,6 +434,7 @@ import Prelude hiding (concatMap, filter, foldr, map, take)
434-- >>> :m
434-- >>> :m
435-- >>> :set -XFlexibleContexts
435-- >>> :set -XFlexibleContexts
436-- >>> import Data.Maybe (fromJust, isJust)
436-- >>> import Data.Maybe (fromJust, isJust)
437+-- >>> import Data.Monoid (Endo(..))
437-- >>> import Streamly.Data.Fold (Fold)
438-- >>> import Streamly.Data.Fold (Fold)
438-- >>> import Streamly.Internal.Data.Stream.Type (Stream)
439-- >>> import Streamly.Internal.Data.Stream.Type (Stream)
439-- >>> import qualified Data.Foldable as Foldable
440-- >>> import qualified Data.Foldable as Foldable
@@ -504,8 +505,6 @@ rmapM f (Fold step initial extract) = Fold step1 initial1 (extract >=> f)
504-- mkfoldlx step initial extract = fmap extract (foldl' step initial)
505-- mkfoldlx step initial extract = fmap extract (foldl' step initial)
505-- @
506-- @
506--
507--
507−-- See also: @Streamly.Prelude.foldl'@
508−--
509{-# INLINE foldl' #-}
508{-# INLINE foldl' #-}
510foldl' :: Monad m => (b -> a -> b) -> b -> Fold m a b
509foldl' :: Monad m => (b -> a -> b) -> b -> Fold m a b
511foldl' step initial =
510foldl' step initial =
@@ -524,8 +523,6 @@ foldl' step initial =
524-- mkFoldlxM step initial extract = rmapM extract (foldlM' step initial)
523-- mkFoldlxM step initial extract = rmapM extract (foldlM' step initial)
525-- @
524-- @
526--
525--
527−-- See also: @Streamly.Prelude.foldlM'@
528−--
529{-# INLINE foldlM' #-}
526{-# INLINE foldlM' #-}
530foldlM' :: Monad m => (b -> a -> m b) -> m b -> Fold m a b
527foldlM' :: Monad m => (b -> a -> m b) -> m b -> Fold m a b
531foldlM' step initial =
528foldlM' step initial =
@@ -534,8 +531,6 @@ foldlM' step initial =
534-- | Make a strict left fold, for non-empty streams, using first element as the
531-- | Make a strict left fold, for non-empty streams, using first element as the
535-- starting value. Returns Nothing if the stream is empty.
532-- starting value. Returns Nothing if the stream is empty.
536--
533--
537−-- See also: @Streamly.Prelude.foldl1'@
538−--
539-- /Pre-release/
534-- /Pre-release/
540{-# INLINE foldl1' #-}
535{-# INLINE foldl1' #-}
541foldl1' :: Monad m => (a -> a -> a) -> Fold m a (Maybe a)
536foldl1' :: Monad m => (a -> a -> a) -> Fold m a (Maybe a)
@@ -552,34 +547,43 @@ foldl1' step = fmap toMaybe $ foldl' step1 Nothing'
552
547
553-- | Make a fold using a right fold style step function and a terminal value.
548-- | Make a fold using a right fold style step function and a terminal value.
554-- It performs a strict right fold via a left fold using function composition.
549-- 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
556-- structures in memory. For reductions this will be very inefficient.
551-- structures in memory. For reductions this will be very inefficient.
557--
552--
558−-- For example,
553+-- Definitions:
559--
554--
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
561--
557--
562−-- See also: 'Streamly.Prelude.foldr'
558+-- Example:
563--
559--
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." #-}
564{-# INLINE foldr #-}
568{-# INLINE foldr #-}
565foldr :: Monad m => (a -> b -> b) -> b -> Fold m a b
569foldr :: 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'
567
571
568-- XXX we have not seen any use of this yet, not releasing until we have a use
572-- XXX we have not seen any use of this yet, not releasing until we have a use
569-- case.
573-- case.
574+
575+-- | Like foldr' but with a monadic step function.
570--
576--
571−-- | Like 'foldr' but with a monadic step function.
577+-- Example:
572−--
573−-- For example,
574--
578--
575−-- > toList = foldrM (\a xs -> return $ a : xs) (return [])
579+-- >>> toList = Fold.foldrM' (\a xs -> return $ a : xs) (return [])
576--
580--
577−-- See also: 'Streamly.Prelude.foldrM'
581+-- See also: 'Streamly.Internal.Data.Stream.foldrM'
578--
582--
579-- /Pre-release/
583-- /Pre-release/
580−{-# INLINE foldrM #-}
584+{-# INLINE foldrM' #-}
581−foldrM :: Monad m => (a -> b -> m b) -> m b -> Fold m a b
585+foldrM' :: Monad m => (a -> b -> m b) -> m b -> Fold m a b
582−foldrM g z =
586+foldrM' g z =
583 rmapM (z >>=) $ foldlM' (\f x -> return $ g x >=> f) (return return)
587 rmapM (z >>=) $ foldlM' (\f x -> return $ g x >=> f) (return return)
584
588
585------------------------------------------------------------------------------
589------------------------------------------------------------------------------
@@ -644,7 +648,8 @@ fromRefold (Refold step inject extract) c =
644-- | A fold that drains all its input, running the effects and discarding the
648-- | A fold that drains all its input, running the effects and discarding the
645-- results.
649-- results.
646--
650--
647−-- > drain = drainBy (const (return ()))
651+-- >>> drain = Fold.drainMapM (const (return ()))
652+-- >>> drain = Fold.foldl' (\_ _ -> ()) ()
648--
653--
649{-# INLINE drain #-}
654{-# INLINE drain #-}
650drain :: Monad m => Fold m a ()
655drain :: Monad m => Fold m a ()
@@ -656,11 +661,11 @@ drain = foldl' (\_ _ -> ()) ()
656-- very inefficient, consider using "Streamly.Data.Array.Unboxed"
661-- very inefficient, consider using "Streamly.Data.Array.Unboxed"
657-- instead.
662-- instead.
658--
663--
659−-- > toList = foldr (:) []
664+-- >>> toList = Fold.foldr' (:) []
660--
665--
661{-# INLINE toList #-}
666{-# INLINE toList #-}
662toList :: Monad m => Fold m a [a]
667toList :: Monad m => Fold m a [a]
663−toList = foldr (:) []
668+toList = foldr' (:) []
664
669
665-- | Buffers the input stream to a pure stream in the reverse order of the
670-- | Buffers the input stream to a pure stream in the reverse order of the
666-- input.
671-- input.
@@ -702,9 +707,28 @@ instance Functor m => Functor (Fold m a) where
702 step s b = fmap2 f (step1 s b)
707 step s b = fmap2 f (step1 s b)
703 fmap2 g = fmap (fmap g)
708 fmap2 g = fmap (fmap g)
704
709
705−-- This is the dual of stream "fromPure".
710+-- XXX These are singleton folds that are closed for input. The correspondence
706−--
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+
707−-- | A fold that always yields a pure value without consuming any input.
730+-- | Make a fold that yields the supplied value without consuming any further
731+-- input.
708--
732--
709-- /Pre-release/
733-- /Pre-release/
710--
734--
@@ -712,10 +736,8 @@ instance Functor m => Functor (Fold m a) where
712fromPure :: Applicative m => b -> Fold m a b
736fromPure :: Applicative m => b -> Fold m a b
713fromPure b = Fold undefined (pure $ Done b) pure
737fromPure b = Fold undefined (pure $ Done b) pure
714
738
715−-- This is the dual of stream "fromEffect".
716−--
717−-- | A fold that always yields the result of an effectful action without
739+-- | Make a fold that yields the result of the supplied effectful action
718−-- consuming any input.
740+-- without consuming any further input.
719--
741--
720-- /Pre-release/
742-- /Pre-release/
721--
743--
@@ -732,17 +754,22 @@ data SeqFoldState sl f sr = SeqFoldL !sl | SeqFoldR !f !sr
732-- or if the input stream is over, the outputs of the two folds are combined
754-- or if the input stream is over, the outputs of the two folds are combined
733-- using the supplied function.
755-- using the supplied function.
734--
756--
757+-- Example:
758+--
759+-- >>> header = Fold.take 8 Fold.toList
735−-- >>> f = Fold.serialWith (,) (Fold.take 8 Fold.toList) (Fold.takeEndBy (== '\n') Fold.toList)
760+-- >>> line = Fold.takeEndBy (== '\n') Fold.toList
761+-- >>> f = Fold.serialWith (,) header line
736-- >>> Stream.fold f $ Stream.fromList "header: hello\n"
762-- >>> Stream.fold f $ Stream.fromList "header: hello\n"
737-- ("header: ","hello\n")
763-- ("header: ","hello\n")
738--
764--
739−-- Note: This is dual to appending streams using 'Streamly.Prelude.serial'.
765+-- Note: This is dual to appending streams using 'Data.Stream.append'.
740--
766--
741-- Note: this implementation allows for stream fusion but has quadratic time
767-- Note: this implementation allows for stream fusion but has quadratic time
742-- complexity, because each composition adds a new branch that each subsequent
768-- complexity, because each composition adds a new branch that each subsequent
743-- fold's input element has to traverse, therefore, it cannot scale to a large
769-- fold's input element has to traverse, therefore, it cannot scale to a large
744-- number of compositions. After around 100 compositions the performance starts
770-- 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.
746--
773--
747-- /Time: O(n^2) where n is the number of compositions./
774-- /Time: O(n^2) where n is the number of compositions./
748--
775--
@@ -828,16 +855,20 @@ data TeeState sL sR bL bR
828-- | @teeWith k f1 f2@ distributes its input to both @f1@ and @f2@ until both
855-- | @teeWith k f1 f2@ distributes its input to both @f1@ and @f2@ until both
829-- of them terminate and combines their output using @k@.
856-- of them terminate and combines their output using @k@.
830--
857--
858+-- Definition:
859+--
860+-- >>> teeWith k f1 f2 = fmap (uncurry k) (Fold.tee f1 f2)
861+--
862+-- Example:
863+--
831-- >>> avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)
864-- >>> avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)
832-- >>> Stream.fold avg $ Stream.fromList [1.0..100.0]
865-- >>> Stream.fold avg $ Stream.fromList [1.0..100.0]
833-- 50.5
866-- 50.5
834--
867--
835−-- > teeWith k f1 f2 = fmap (uncurry k) ((Fold.tee f1 f2)
836−--
837-- For applicative composition using this combinator see
868-- For applicative composition using this combinator see
838−-- "Streamly.Internal.Data.Fold.Tee".
869+-- "Streamly.Data.Fold.Tee".
839--
870--
840−-- See also: "Streamly.Internal.Data.Fold.Tee"
871+-- See also: "Streamly.Data.Fold.Tee"
841--
872--
842{-# INLINE teeWith #-}
873{-# INLINE teeWith #-}
843teeWith :: Monad m => (a -> b -> c) -> Fold m x a -> Fold m x b -> Fold m x c
874teeWith :: 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
1092
1123
1093-- | @lmap f fold@ maps the function @f@ on the input of the fold.
1124-- | @lmap f fold@ maps the function @f@ on the input of the fold.
1094--
1125--
1095−-- >>> Stream.fold (Fold.lmap (\x -> x * x) Fold.sum) (Stream.enumerateFromTo 1 100)
1126+-- Definition:
1096−-- 338350
1127+--
1128+-- >>> lmap = Fold.lmapM return
1097--
1129--
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
1099--
1135--
1100{-# INLINE lmap #-}
1136{-# INLINE lmap #-}
1101lmap :: (a -> b) -> Fold m b r -> Fold m a r
1137lmap :: (a -> b) -> Fold m b r -> Fold m a r
@@ -1247,6 +1283,10 @@ rights = filter isRight . lmap (fromRight undefined)
1247-- | Remove the either wrapper and flatten both lefts and as well as rights in
1283-- | Remove the either wrapper and flatten both lefts and as well as rights in
1248-- the output stream.
1284-- the output stream.
1249--
1285--
1286+-- Definition:
1287+--
1288+-- >>> both = Fold.lmap (either id id)
1289+--
1250-- /Pre-release/
1290-- /Pre-release/
1251--
1291--
1252{-# INLINE both #-}
1292{-# INLINE both #-}
@@ -1328,29 +1368,18 @@ take n (Fold fstep finitial fextract) = Fold step initial extract
1328-- Nesting
1368-- Nesting
1329------------------------------------------------------------------------------
1369------------------------------------------------------------------------------
1330
1370
1371+-- Similar to the comonad "duplicate" operation.
1372+
1331-- | 'duplicate' provides the ability to run a fold in parts. The duplicated
1373-- | 'duplicate' provides the ability to run a fold in parts. The duplicated
1332-- fold consumes the input and returns the same fold as output instead of
1374-- fold consumes the input and returns the same fold as output instead of
1333-- returning the final result, the returned fold can be run later to consume
1375-- returning the final result, the returned fold can be run later to consume
1334-- more input.
1376-- more input.
1335--
1377--
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−--
1351-- 'duplicate' essentially appends a stream to the fold without finishing the
1378-- 'duplicate' essentially appends a stream to the fold without finishing the
1352-- fold. Compare with 'snoc' which appends a singleton value to the fold.
1379-- fold. Compare with 'snoc' which appends a singleton value to the fold.
1353--
1380--
1381+-- See also 'Streamly.Internal.Data.Stream.build'.
1382+--
1354-- /Pre-release/
1383-- /Pre-release/
1355{-# INLINE duplicate #-}
1384{-# INLINE duplicate #-}
1356duplicate :: Monad m => Fold m a b -> Fold m a (Fold m a b)
1385duplicate :: Monad m => Fold m a b -> Fold m a (Fold m a b)
@@ -1363,23 +1392,94 @@ duplicate (Fold step1 initial1 extract1) =
1363
1392
1364 step s a = second fromPure <$> step1 s a
1393 step s a = second fromPure <$> step1 s a
1365
1394
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+
1366−-- | Run the initialization effect of a fold. The returned fold would use the
1399+-- | Evaluate the initialization effect of a fold. If we are building the fold
1367−-- value returned by this effect as its initial value.
1400+-- by chaining lazy actions in fold init this would reduce the actions to a
1401+-- strict accumulator value.
1368--
1402--
1369-- /Pre-release/
1403-- /Pre-release/
1370−{-# INLINE initialize #-}
1404+{-# INLINE reduce #-}
1371−initialize :: Monad m => Fold m a b -> m (Fold m a b)
1405+reduce :: Monad m => Fold m a b -> m (Fold m a b)
1372−initialize (Fold step initial extract) = do
1406+reduce (Fold step initial extract) = do
1373 i <- initial
1407 i <- initial
1374 return $ Fold step (return i) extract
1408 return $ Fold step (return i) extract
1375
1409
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+
1376−-- | Append a singleton value to the fold.
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:
1377--
1436--
1378-- >>> import qualified Data.Foldable as Foldable
1437-- >>> 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]
1380-- [1,2,3]
1439-- [1,2,3]
1381--
1440--
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]
1383--
1483--
1384-- /Pre-release/
1484-- /Pre-release/
1385{-# INLINE snoc #-}
1485{-# INLINE snoc #-}
@@ -1391,30 +1491,54 @@ snoc (Fold step initial extract) a = do
1391 Done _ -> return res
1491 Done _ -> return res
1392 return $ Fold step (return r) extract
1492 return $ Fold step (return r) extract
1393
1493
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
1395--
1502--
1503+-- Example:
1504+--
1396−-- >>> Fold.finish Fold.toList
1505+-- >>> Fold.extractM Fold.toList
1397-- []
1506-- []
1398--
1507--
1399-- /Pre-release/
1508-- /Pre-release/
1400−{-# INLINE finish #-}
1509+{-# INLINE extractM #-}
1401−finish :: Monad m => Fold m a b -> m b
1510+extractM :: Monad m => Fold m a b -> m b
1402−finish (Fold _ initial extract) = do
1511+extractM (Fold _ initial extract) = do
1403 res <- initial
1512 res <- initial
1404 case res of
1513 case res of
1405 Partial fs -> extract fs
1514 Partial fs -> extract fs
1406 Done b -> return b
1515 Done b -> return b
1407
1516
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+
1408−-- | Check if the fold is done and can take no more input.
1532+-- | Check if the fold has terminated and can take no more input.
1409--
1533--
1410-- /Pre-release/
1534-- /Pre-release/
1411−{-# INLINE isDone #-}
1535+{-# INLINE isClosed #-}
1412−isDone :: Monad m => Fold m a b -> m (Maybe b)
1536+isClosed :: Monad m => Fold m a b -> m Bool
1413−isDone (Fold _ initial _) = do
1537+isClosed (Fold _ initial _) = do
1414 res <- initial
1538 res <- initial
1415 return $ case res of
1539 return $ case res of
1416− Partial _ -> Nothing
1540+ Partial _ -> False
1417− Done b -> Just b
1541+ Done _ -> True
1418
1542
1419------------------------------------------------------------------------------
1543------------------------------------------------------------------------------
1420-- Parsing
1544-- Parsing
@@ -1440,7 +1564,7 @@ data ManyState s1 s2
1440--
1564--
1441-- Stops when @collect@ stops.
1565-- Stops when @collect@ stops.
1442--
1566--
1443−-- See also: 'Streamly.Prelude.concatMap', 'Streamly.Prelude.foldMany'
1567+-- See also: 'Data.Stream.concatMap', 'Data.Stream.foldMany'
1444--
1568--
1445{-# INLINE many #-}
1569{-# INLINE many #-}
1446many :: Monad m => Fold m a b -> Fold m b c -> Fold m a c
1570many :: 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) =
1499--
1623--
1500-- /Internal/
1624-- /Internal/
1501--
1625--
1502−-- /See also: 'Streamly.Prelude.concatMap', 'Streamly.Prelude.foldMany'/
1626+-- See also: 'Data.Stream.concatMap', 'Data.Stream.foldMany'
1503--
1627--
1504{-# INLINE manyPost #-}
1628{-# INLINE manyPost #-}
1505manyPost :: Monad m => Fold m a b -> Fold m b c -> Fold m a c
1629manyPost :: 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) =
1544-- of @n@ items in the input stream and supplies the result to the @collect@
1668-- of @n@ items in the input stream and supplies the result to the @collect@
1545-- fold.
1669-- fold.
1546--
1670--
1671+-- Definition:
1672+--
1673+-- >>> chunksOf n split = Fold.many (Fold.take n split)
1674+--
1675+-- Example:
1676+--
1547-- >>> twos = Fold.chunksOf 2 Fold.toList Fold.toList
1677-- >>> twos = Fold.chunksOf 2 Fold.toList Fold.toList
1548-- >>> Stream.fold twos $ Stream.fromList [1..10]
1678-- >>> Stream.fold twos $ Stream.fromList [1..10]
1549-- [[1,2],[3,4],[5,6],[7,8],[9,10]]
1679-- [[1,2],[3,4],[5,6],[7,8],[9,10]]
1550--
1680--
1551−-- > chunksOf n split = many (take n split)
1552−--
1553-- Stops when @collect@ stops.
1681-- Stops when @collect@ stops.
1554--
1682--
1555{-# INLINE chunksOf #-}
1683{-# INLINE chunksOf #-}
@@ -1665,4 +1793,5 @@ refoldMany1 (Refold sstep sinject sextract) (Fold cstep cinitial cextract) =
1665-- /Internal/
1793-- /Internal/
1666{-# INLINE refold #-}
1794{-# INLINE refold #-}
1667refold :: Monad m => Refold m b a c -> Fold m a b -> Fold m a c
1795refold :: 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
54 , maximum
54 , maximum
55 , range
55 , range
56 , mean
56 , mean
57−
58− -- ** Distribution
59− , frequency
60 )
57 )
61where
58where
62
59
@@ -68,7 +65,6 @@ import Streamly.Internal.Data.Fold.Type (Fold(..), Step(..))
68import Streamly.Internal.Data.Tuple.Strict
65import Streamly.Internal.Data.Tuple.Strict
69 (Tuple'(..), Tuple3Fused' (Tuple3Fused'))
66 (Tuple'(..), Tuple3Fused' (Tuple3Fused'))
70
67
71−import qualified Data.Map as Map
72import qualified Streamly.Internal.Data.Fold.Type as Fold
68import qualified Streamly.Internal.Data.Fold.Type as Fold
73import qualified Streamly.Internal.Data.Ring.Unboxed as Ring
69import qualified Streamly.Internal.Data.Ring.Unboxed as Ring
74
70
@@ -353,36 +349,3 @@ maximum n = fmap (fmap snd) $ range n
353{-# INLINE mean #-}
349{-# INLINE mean #-}
354mean :: forall m a. (Monad m, Fractional a) => Fold m (a, Maybe a) a
350mean :: forall m a. (Monad m, Fractional a) => Fold m (a, Maybe a) a
355mean = Fold.teeWith (/) sum length
351mean = 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
105 , listEq
105 , listEq
106 , listEqBy
106 , listEqBy
107 , eqBy
107 , eqBy
108+ , subsequenceBy
108
109
109 -- ** By predicate
110 -- ** By predicate
110 , takeWhileP
111 , takeWhileP
@@ -442,6 +443,8 @@ satisfy = D.toParserK . D.satisfy
442one :: Monad m => Parser m a a
443one :: Monad m => Parser m a a
443one = satisfy $ const True
444one = satisfy $ const True
444
445
446+-- Alternate names: "only", "onlyThis".
447+
445-- | Match a specific element.
448-- | Match a specific element.
446--
449--
447-- >>> oneEq x = Parser.satisfy (== x)
450-- >>> oneEq x = Parser.satisfy (== x)
@@ -450,6 +453,8 @@ one = satisfy $ const True
450oneEq :: (Monad m, Eq a) => a -> Parser m a a
453oneEq :: (Monad m, Eq a) => a -> Parser m a a
451oneEq x = satisfy (== x)
454oneEq x = satisfy (== x)
452
455
456+-- Alternate names: "exclude", "notThis".
457+
453-- | Match anything other than the supplied element.
458-- | Match anything other than the supplied element.
454--
459--
455-- >>> oneNotEq x = Parser.satisfy (/= x)
460-- >>> oneNotEq x = Parser.satisfy (/= x)
@@ -995,27 +1000,40 @@ groupByRollingEither :: Monad m =>
995 (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser m a (Either b c)
1000 (a -> a -> Bool) -> Fold m a b -> Fold m a c -> Parser m a (Either b c)
996groupByRollingEither eq f1 = D.toParserK . D.groupByRollingEither eq f1
1001groupByRollingEither eq f1 = D.toParserK . D.groupByRollingEither eq f1
997
1002
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+
998-- | Match the given sequence of elements using the given comparison function.
1016-- | Match the given sequence of elements using the given comparison function.
999-- Returns the original sequence if successful.
1017-- Returns the original sequence if successful.
1000--
1018--
1019+-- Definition:
1020+--
1021+-- >>> listEqBy cmp xs = Parser.eqBy cmp (Stream.fromList xs) *> Parser.fromPure xs
1022+--
1023+-- Examples:
1024+--
1001-- >>> Stream.parse (Parser.listEqBy (==) "string") $ Stream.fromList "string"
1025-- >>> Stream.parse (Parser.listEqBy (==) "string") $ Stream.fromList "string"
1002-- "string"
1026-- "string"
1003--
1027--
1004-- >>> Stream.parse (Parser.listEqBy (==) "mismatch") $ Stream.fromList "match"
1028-- >>> Stream.parse (Parser.listEqBy (==) "mismatch") $ Stream.fromList "match"
1005−-- *** Exception: ParseError "listEqBy: failed, yet to match 7 elements"
1029+-- *** Exception: ParseError "eqBy: mismtach occurred"
1006--
1030--
1007{-# INLINE listEqBy #-}
1031{-# INLINE listEqBy #-}
1008listEqBy :: Monad m => (a -> a -> Bool) -> [a] -> Parser m a [a]
1032listEqBy :: Monad m => (a -> a -> Bool) -> [a] -> Parser m a [a]
1009−listEqBy cmp xs = D.toParserK (D.listEqBy cmp xs)
1033+-- listEqBy cmp xs = D.toParserK (D.listEqBy cmp xs)
1010−
1034+listEqBy cmp xs = eqBy cmp (Stream.fromList xs) *> fromPure xs
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
1018
1035
1036+-- Rename to "list".
1019-- | Match the input sequence with the supplied list and return it if
1037-- | Match the input sequence with the supplied list and return it if
1020-- successful.
1038-- successful.
1021--
1039--
@@ -1025,6 +1043,29 @@ eqBy cmp = D.toParserK . D.eqBy cmp . Stream.toStreamD
1025listEq :: (Monad m, Eq a) => [a] -> Parser m a [a]
1043listEq :: (Monad m, Eq a) => [a] -> Parser m a [a]
1026listEq = listEqBy (==)
1044listEq = listEqBy (==)
1027
1045
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+
1028-------------------------------------------------------------------------------
1069-------------------------------------------------------------------------------
1029-- nested parsers
1070-- nested parsers
1030-------------------------------------------------------------------------------
1071-------------------------------------------------------------------------------
@@ -1432,7 +1473,7 @@ sepBy1 :: Monad m =>
1432 Parser m a b -> Parser m a x -> Fold m b c -> Parser m a c
1473 Parser m a b -> Parser m a x -> Fold m b c -> Parser m a c
1433sepBy1 p sep sink = do
1474sepBy1 p sep sink = do
1434 x <- p
1475 x <- p
1435− f <- fromEffect $ FL.initialize sink
1476+ f <- fromEffect $ FL.reduce sink
1436 f1 <- fromEffect $ FL.snoc f x
1477 f1 <- fromEffect $ FL.snoc f x
1437 many (sep >> p) f1
1478 many (sep >> p) f1
1438
1479
core/src/Streamly/Internal/Data/Stream/Bottom.hs+52−40
@@ -21,13 +21,16 @@ module Streamly.Internal.Data.Stream.Bottom
21
21
22 -- * Folds
22 -- * Folds
23 , fold
23 , fold
24− , foldContinue
25 , foldBreak
24 , foldBreak
26 , foldBreak2
25 , foldBreak2
27 , foldEither
26 , foldEither
28 , foldEither2
27 , foldEither2
29 , foldConcat
28 , foldConcat
30
29
30+ -- * Builders
31+ , build
32+ , buildl
33+
31 -- * Scans
34 -- * Scans
32 , smapM
35 , smapM
33 -- $smapM_Notes
36 -- $smapM_Notes
@@ -75,7 +78,6 @@ import Streamly.Internal.Data.SVar.Type (defState)
75
78
76import qualified Streamly.Internal.Data.Array.Unboxed.Type as A
79import qualified Streamly.Internal.Data.Array.Unboxed.Type as A
77import qualified Streamly.Internal.Data.Fold as Fold
80import qualified Streamly.Internal.Data.Fold as Fold
78−import qualified Streamly.Internal.Data.Stream.Common as Common
79import qualified Streamly.Internal.Data.Stream.StreamK as K
81import qualified Streamly.Internal.Data.Stream.StreamK as K
80import qualified Streamly.Internal.Data.Stream.StreamD as D
82import qualified Streamly.Internal.Data.Stream.StreamD as D
81
83
@@ -86,7 +88,7 @@ import Streamly.Internal.Data.Stream.Type
86--
88--
87-- $setup
89-- $setup
88-- >>> :m
90-- >>> :m
89−-- >>> import Control.Monad (join)
91+-- >>> import Control.Monad (join, (>=>), (<=<))
90-- >>> import Control.Monad.Trans.Class (lift)
92-- >>> import Control.Monad.Trans.Class (lift)
91-- >>> import Data.Function (fix, (&))
93-- >>> import Data.Function (fix, (&))
92-- >>> import Data.Maybe (fromJust, isJust)
94-- >>> import Data.Maybe (fromJust, isJust)
@@ -99,20 +101,6 @@ import Streamly.Internal.Data.Stream.Type
99-- >>> import qualified Streamly.Internal.Data.Parser as Parser
101-- >>> import qualified Streamly.Internal.Data.Parser as Parser
100-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
102-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
101
103
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−
116------------------------------------------------------------------------------
104------------------------------------------------------------------------------
117-- Generation - Time related
105-- Generation - Time related
118------------------------------------------------------------------------------
106------------------------------------------------------------------------------
@@ -126,7 +114,7 @@ fromList = fromStreamK . Common.fromList
126-- terms of CPU usage. Any granularity lower than 1 ms is treated as 1 ms.
114-- terms of CPU usage. Any granularity lower than 1 ms is treated as 1 ms.
127--
115--
128-- >>> import Control.Concurrent (threadDelay)
116-- >>> import Control.Concurrent (threadDelay)
129−-- >>> f = Fold.drainBy (\x -> print x >> threadDelay 1000000)
117+-- >>> f = Fold.drainMapM (\x -> print x >> threadDelay 1000000)
130-- >>> Stream.fold f $ Stream.take 3 $ Stream.timesWith 0.01
118-- >>> Stream.fold f $ Stream.take 3 $ Stream.timesWith 0.01
131-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
119-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
132-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
120-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
@@ -145,7 +133,7 @@ timesWith g = fromStreamD $ D.times g
145-- expensive in terms of CPU usage. Any granularity lower than 1 ms is treated
133-- expensive in terms of CPU usage. Any granularity lower than 1 ms is treated
146-- as 1 ms.
134-- as 1 ms.
147--
135--
148−-- >>> f = Fold.drainBy print
136+-- >>> f = Fold.drainMapM print
149-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.absTimesWith 0.01
137-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.absTimesWith 0.01
150-- AbsTime (TimeSpec {sec = ..., nsec = ...})
138-- AbsTime (TimeSpec {sec = ..., nsec = ...})
151-- AbsTime (TimeSpec {sec = ..., nsec = ...})
139-- AbsTime (TimeSpec {sec = ..., nsec = ...})
@@ -164,7 +152,7 @@ absTimesWith = fmap (uncurry addToAbsTime64) . timesWith
164-- clock is more expensive in terms of CPU usage. Any granularity lower than 1
152-- clock is more expensive in terms of CPU usage. Any granularity lower than 1
165-- ms is treated as 1 ms.
153-- ms is treated as 1 ms.
166--
154--
167−-- >>> f = Fold.drainBy print
155+-- >>> f = Fold.drainMapM print
168-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.relTimesWith 0.01
156-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.relTimesWith 0.01
169-- RelTime64 (NanoSecond64 ...)
157-- RelTime64 (NanoSecond64 ...)
170-- RelTime64 (NanoSecond64 ...)
158-- RelTime64 (NanoSecond64 ...)
@@ -182,40 +170,64 @@ relTimesWith = fmap snd . timesWith
182-- Elimination - Running a Fold
170-- Elimination - Running a Fold
183------------------------------------------------------------------------------
171------------------------------------------------------------------------------
184
172
185−-- | We can create higher order folds using 'foldContinue'. We can fold a
173+-- | Append a stream to a fold lazily to build an accumulator incrementally.
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:
188--
174--
189−-- >>> concatFold = Prelude.foldl Stream.foldContinue Fold.sum
175+-- Example, to continue folding a list of streams on the same sum fold:
190--
176--
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
192--
181--
193−{-# INLINE foldContinue #-}
182+{-# INLINE buildl #-}
194−foldContinue :: Monad m => Fold m a b -> Stream m a -> Fold m a b
183+buildl :: Monad m => Fold m a b -> Stream m a -> Fold m a b
195−foldContinue f s = D.foldContinue f $ toStreamD s
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)
196
205
197-- | Fold a stream using the supplied left 'Fold' and reducing the resulting
206-- | Fold a stream using the supplied left 'Fold' and reducing the resulting
198-- expression strictly at each step. The behavior is similar to 'foldl''. A
207-- expression strictly at each step. The behavior is similar to 'foldl''. A
199-- 'Fold' can terminate early without consuming the full stream. See the
208-- 'Fold' can terminate early without consuming the full stream. See the
200-- documentation of individual 'Fold's for termination behavior.
209-- documentation of individual 'Fold's for termination behavior.
201--
210--
202−-- >>> Stream.fold Fold.sum (Stream.enumerateFromTo 1 100)
211+-- Definitions:
203−-- 5050
204--
212--
205−-- Folds never fail, therefore, they produce a default value even when no input
213+-- >>> fold f = fmap fst . Stream.foldBreak f
206−-- is provided. It means we can always fold an empty stream and get a valid
214+-- >>> fold f = Fold.extractM . Stream.buildl f
207−-- result. For example:
215+-- >>> fold f = Fold.extractM <=< Stream.build f
216+-- >>> fold f = Stream.parse (Parser.fromFold f)
208--
217--
209−-- >>> Stream.fold Fold.sum Stream.nil
218+-- Example:
210−-- 0
211--
219--
212−-- >>> fold f = Stream.parse (Parser.fromFold f)
220+-- >>> Stream.fold Fold.sum (Stream.enumerateFromTo 1 100)
221+-- 5050
213--
222--
214{-# INLINE fold #-}
223{-# INLINE fold #-}
215fold :: Monad m => Fold m a b -> Stream m a -> m b
224fold :: Monad m => Fold m a b -> Stream m a -> m b
216fold fl strm = D.fold fl $ D.fromStreamK $ toStreamK strm
225fold fl strm = D.fold fl $ D.fromStreamK $ toStreamK strm
217
226
227+-- Alternative name foldSome, but may be confused vs foldMany.
228+
218−-- | Like 'fold' but also returns the remaining stream.
229+-- | Like 'fold' but also returns the remaining stream. The resulting stream
230+-- would be 'Stream.nil' if the stream finished before the fold.
219--
231--
220-- /Not fused/
232-- /Not fused/
221--
233--
@@ -246,9 +258,9 @@ foldBreak2 fl strm = fmap f $ D.foldBreak fl $ toStreamD strm
246
258
247 f (b, str) = (b, fromStreamD str)
259 f (b, str) = (b, fromStreamD str)
248
260
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.
250-- Instead of supplying the input stream in one go we can run the fold multiple
262-- 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
252-- has not yet finished it returns a fold that can be run again otherwise it
264-- has not yet finished it returns a fold that can be run again otherwise it
253-- returns the fold result and the residual stream.
265-- returns the fold result and the residual stream.
254--
266--
@@ -372,7 +384,7 @@ map f = fromStreamD . D.map f . toStreamD
372-- Stream.fold Fold.toList
384-- Stream.fold Fold.toList
373-- $ fmap (fromJust . fst)
385-- $ fmap (fromJust . fst)
374-- $ Stream.takeWhile (\(_,x) -> x <= 10)
386-- $ Stream.takeWhile (\(_,x) -> x <= 10)
375−-- $ Stream.postscan (Fold.tee Fold.last avg) s
387+-- $ Stream.postscan (Fold.tee Fold.latest avg) s
376-- :}
388-- :}
377-- [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]
389-- [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]
378--
390--
core/src/Streamly/Internal/Data/Stream/Eliminate.hs+17−5
@@ -24,7 +24,14 @@ module Streamly.Internal.Data.Stream.Eliminate
24 -- See "Streamly.Internal.Data.Fold".
24 -- See "Streamly.Internal.Data.Fold".
25 fold
25 fold
26 , foldBreak
26 , foldBreak
27− , foldContinue
27+ , foldBreak2
28+ , foldEither
29+ , foldEither2
30+ , foldConcat
31+
32+ -- * Builders
33+ , build
34+ , buildl
28
35
29 -- * Running a 'Parser'
36 -- * Running a 'Parser'
30 -- "Streamly.Internal.Data.Parser".
37 -- "Streamly.Internal.Data.Parser".
@@ -106,10 +113,15 @@ import Prelude hiding (foldr, init, reverse)
106-- 'Nothing'. If the stream is non-empty, returns @Just (a, ma)@, where @a@ is
113-- 'Nothing'. If the stream is non-empty, returns @Just (a, ma)@, where @a@ is
107-- the head of the stream and @ma@ its tail.
114-- the head of the stream and @ma@ its tail.
108--
115--
116+-- Properties:
117+--
118+-- >>> Nothing <- Stream.uncons Stream.nil
119+-- >>> Just ("a", t) <- Stream.uncons (Stream.cons "a" Stream.nil)
120+--
109−-- This can be used to do pretty much anything in an imperative manner, as it
121+-- This can be used to consume the stream in an imperative manner one element
110−-- just breaks down the stream into individual elements and we can loop over
122+-- at a time, as it just breaks down the stream into individual elements and we
111−-- them as we deem fit. For example, this can be used to convert a streamly
123+-- can loop over them as we deem fit. For example, this can be used to convert
112−-- stream into other stream types.
124+-- a streamly stream into other stream types.
113--
125--
114-- All the folds in this module can be expressed in terms of 'uncons', however,
126-- All the folds in this module can be expressed in terms of 'uncons', however,
115-- this is generally less efficient than specific folds because it takes apart
127-- 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
19 , finallyIO
19 , finallyIO
20 , ghandle
20 , ghandle
21 , handle
21 , handle
22− , retry
23 )
22 )
24where
23where
25
24
26import Control.Exception (Exception)
25import Control.Exception (Exception)
27import Control.Monad.Catch (MonadCatch)
26import Control.Monad.Catch (MonadCatch)
28import Control.Monad.IO.Class (MonadIO)
27import Control.Monad.IO.Class (MonadIO)
29−import Data.Map.Strict (Map)
30import Streamly.Internal.Data.Stream.Type (Stream, fromStreamD, toStreamD)
28import Streamly.Internal.Data.Stream.Type (Stream, fromStreamD, toStreamD)
31
29
32import qualified Streamly.Internal.Data.Stream.StreamD as D
30import qualified Streamly.Internal.Data.Stream.StreamD as D
@@ -220,46 +218,3 @@ handle :: (MonadCatch m, Exception e)
220 => (e -> Stream m a) -> Stream m a -> Stream m a
218 => (e -> Stream m a) -> Stream m a -> Stream m a
221handle handler xs =
219handle handler xs =
222 fromStreamD $ D.handle (toStreamD . handler) $ toStreamD xs
220 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
225-- (epoch) denoting the start of the stream and the second component is a time
225-- (epoch) denoting the start of the stream and the second component is a time
226-- relative to the reference.
226-- relative to the reference.
227--
227--
228−-- >>> f = Fold.drainBy (\x -> print x >> threadDelay 1000000)
228+-- >>> f = Fold.drainMapM (\x -> print x >> threadDelay 1000000)
229-- >>> Stream.fold f $ Stream.take 3 $ Stream.times
229-- >>> Stream.fold f $ Stream.take 3 $ Stream.times
230-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
230-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
231-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
231-- (AbsTime (TimeSpec {sec = ..., nsec = ...}),RelTime64 (NanoSecond64 ...))
@@ -242,7 +242,7 @@ times = timesWith 0.01
242-- | @absTimes@ returns a stream of absolute timestamps using a clock of 10 ms
242-- | @absTimes@ returns a stream of absolute timestamps using a clock of 10 ms
243-- granularity.
243-- granularity.
244--
244--
245−-- >>> f = Fold.drainBy print
245+-- >>> f = Fold.drainMapM print
246-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.absTimes
246-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.absTimes
247-- AbsTime (TimeSpec {sec = ..., nsec = ...})
247-- AbsTime (TimeSpec {sec = ..., nsec = ...})
248-- AbsTime (TimeSpec {sec = ..., nsec = ...})
248-- AbsTime (TimeSpec {sec = ..., nsec = ...})
@@ -259,7 +259,7 @@ absTimes = fmap (uncurry addToAbsTime64) times
259-- | @relTimes@ returns a stream of relative time values starting from 0,
259-- | @relTimes@ returns a stream of relative time values starting from 0,
260-- using a clock of granularity 10 ms.
260-- using a clock of granularity 10 ms.
261--
261--
262−-- >>> f = Fold.drainBy print
262+-- >>> f = Fold.drainMapM print
263-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.relTimes
263-- >>> Stream.fold f $ Stream.delayPre 1 $ Stream.take 3 $ Stream.relTimes
264-- RelTime64 (NanoSecond64 ...)
264-- RelTime64 (NanoSecond64 ...)
265-- RelTime64 (NanoSecond64 ...)
265-- RelTime64 (NanoSecond64 ...)
@@ -373,7 +373,7 @@ iterateM step = fromStreamD . D.iterateM step
373-- >>> import System.IO.Unsafe (unsafeInterleaveIO)
373-- >>> import System.IO.Unsafe (unsafeInterleaveIO)
374--
374--
375-- >>> :{
375-- >>> :{
376−-- main = Stream.fold (Fold.drainBy print) $ Stream.mfix f
376+-- main = Stream.fold (Fold.drainMapM print) $ Stream.mfix f
377-- where
377-- where
378-- f action = do
378-- f action = do
379-- let incr n act = fmap ((+n) . snd) $ unsafeInterleaveIO act
379-- 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
20 , finally
20 , finally
21 , ghandle
21 , ghandle
22 , handle
22 , handle
23− , retry
24 )
23 )
25where
24where
26
25
@@ -29,13 +28,11 @@ where
29import Control.Monad.IO.Class (MonadIO(..))
28import Control.Monad.IO.Class (MonadIO(..))
30import Control.Exception (Exception, SomeException, mask_)
29import Control.Exception (Exception, SomeException, mask_)
31import Control.Monad.Catch (MonadCatch)
30import Control.Monad.Catch (MonadCatch)
32−import Data.Map.Strict (Map)
33import GHC.Exts (inline)
31import GHC.Exts (inline)
34import Streamly.Internal.Data.IOFinalizer
32import Streamly.Internal.Data.IOFinalizer
35 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
33 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
36
34
37import qualified Control.Monad.Catch as MC
35import qualified Control.Monad.Catch as MC
38−import qualified Data.Map.Strict as Map
39
36
40import Streamly.Internal.Data.Stream.StreamD.Type
37import Streamly.Internal.Data.Stream.StreamD.Type
41
38
@@ -369,55 +366,3 @@ _handle f (Stream step state) = Stream step' (Left state)
369 Yield x s -> return $ Yield x (Right (Stream step1 s))
366 Yield x s -> return $ Yield x (Right (Stream step1 s))
370 Skip s -> return $ Skip (Right (Stream step1 s))
367 Skip s -> return $ Skip (Right (Stream step1 s))
371 Stop -> return Stop
368 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
72 , filterM
72 , filterM
73 , deleteBy
73 , deleteBy
74 , uniq
74 , uniq
75− , nub
76
75
77 -- * Trimming
76 -- * Trimming
78 -- | Produce a subset of the stream trimmed at ends.
77 -- | Produce a subset of the stream trimmed at ends.
@@ -133,7 +132,6 @@ import Streamly.Internal.Data.Fold.Type (Fold(..))
133import Streamly.Internal.Data.Pipe.Type (Pipe(..), PipeState(..))
132import Streamly.Internal.Data.Pipe.Type (Pipe(..), PipeState(..))
134import Streamly.Internal.Data.SVar.Type (defState, adaptState)
133import Streamly.Internal.Data.SVar.Type (defState, adaptState)
135
134
136−import qualified Data.Set as Set
137import qualified Streamly.Internal.Data.Fold.Type as FL
135import qualified Streamly.Internal.Data.Fold.Type as FL
138import qualified Streamly.Internal.Data.Pipe.Type as Pipe
136import qualified Streamly.Internal.Data.Pipe.Type as Pipe
139
137
@@ -703,26 +701,6 @@ uniq (Stream step state) = Stream step' (Nothing, state)
703 Skip s -> return $ Skip (Just x, s)
701 Skip s -> return $ Skip (Just x, s)
704 Stop -> return Stop
702 Stop -> return Stop
705
703
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−
726{-# INLINE_NORMAL deleteBy #-}
704{-# INLINE_NORMAL deleteBy #-}
727deleteBy :: Monad m => (a -> a -> Bool) -> a -> Stream m a -> Stream m a
705deleteBy :: Monad m => (a -> a -> Bool) -> a -> Stream m a -> Stream m a
728deleteBy eq x (Stream step state) = Stream step' (state, False)
706deleteBy 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
323
323
324 nil = Stream (\_ _ -> return Stop) ()
324 nil = Stream (\_ _ -> return Stop) ()
325
325
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.
329{-# INLINE_NORMAL foldContinue #-}
326{-# INLINE_NORMAL foldContinue #-}
330foldContinue :: Monad m => Fold m a b -> Stream m a -> Fold m a b
327foldContinue :: Monad m => Fold m a b -> Stream m a -> Fold m a b
331foldContinue (Fold fstep finitial fextract) (Stream sstep state) =
328foldContinue (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
32 -- ** Join operations
32 -- ** Join operations
33 , crossJoin
33 , crossJoin
34 , joinInner
34 , joinInner
35− , joinInnerMap
36 , joinInnerMerge
35 , joinInnerMerge
37 , joinLeft
36 , joinLeft
38 , mergeLeftJoin
37 , mergeLeftJoin
39− , joinLeftMap
40 , joinOuter
38 , joinOuter
41 , mergeOuterJoin
39 , mergeOuterJoin
42− , joinOuterMap
43 )
40 )
44where
41where
45
42
@@ -56,7 +53,6 @@ import Streamly.Internal.Data.Stream.Common ()
56import Streamly.Internal.Data.Stream.Type (Stream, fromStreamD, toStreamD)
53import Streamly.Internal.Data.Stream.Type (Stream, fromStreamD, toStreamD)
57
54
58import qualified Data.List as List
55import qualified Data.List as List
59−import qualified Data.Map.Strict as Map
60import qualified Streamly.Internal.Data.Array as Array
56import qualified Streamly.Internal.Data.Array as Array
61import qualified Streamly.Internal.Data.Array.Unboxed.Mut.Type as MA
57import qualified Streamly.Internal.Data.Array.Unboxed.Mut.Type as MA
62import qualified Streamly.Internal.Data.Fold as Fold
58import qualified Streamly.Internal.Data.Fold as Fold
@@ -211,44 +207,6 @@ joinInner eq s1 s2 = do
211 ) s2
207 ) s2
212 ) s1
208 ) s1
213
209
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−
252-- | Like 'joinInner' but works only on sorted streams.
210-- | Like 'joinInner' but works only on sorted streams.
253--
211--
254-- Space: O(1)
212-- Space: O(1)
@@ -309,28 +267,6 @@ joinLeft eq s1 s2 = Stream.evalStateT (return False) $ do
309 else Stream.nil
267 else Stream.nil
310 Nothing -> return (a, Nothing)
268 Nothing -> return (a, Nothing)
311
269
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−
334-- | Like 'joinLeft' but works only on sorted streams.
270-- | Like 'joinLeft' but works only on sorted streams.
335--
271--
336-- Space: O(1)
272-- Space: O(1)
@@ -412,57 +348,6 @@ joinOuter eq s1 s =
412 else Stream.nil
348 else Stream.nil
413 Nothing -> return (Just a, Nothing)
349 Nothing -> return (Just a, Nothing)
414
350
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−
466-- | Like 'joinOuter' but works only on sorted streams.
351-- | Like 'joinOuter' but works only on sorted streams.
467--
352--
468-- Space: O(1)
353-- Space: O(1)
core/src/Streamly/Internal/Data/Stream/Transform.hs+4−4
@@ -313,7 +313,7 @@ sequence = mapM id
313-- @
313-- @
314--
314--
315-- >>> s = Stream.enumerateFromTo 1 2
315-- >>> 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
317-- 1
317-- 1
318-- 2
318-- 2
319--
319--
@@ -770,7 +770,7 @@ interspersePrefix_ m = mapM (\x -> void m >> return x)
770-- | Introduce a delay of specified seconds between elements of the stream.
770-- | Introduce a delay of specified seconds between elements of the stream.
771--
771--
772-- >>> input = Stream.enumerateFromTo 1 3
772-- >>> 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
774-- 1
774-- 1
775-- 2
775-- 2
776-- 3
776-- 3
@@ -786,7 +786,7 @@ delay n = intersperseM_ $ liftIO $ threadDelay $ round $ n * 1000000
786-- stream.
786-- stream.
787--
787--
788-- >>> input = Stream.enumerateFromTo 1 3
788-- >>> 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
790-- 1
790-- 1
791-- 2
791-- 2
792-- 3
792-- 3
@@ -801,7 +801,7 @@ delayPost n = intersperseSuffix_ $ liftIO $ threadDelay $ round $ n * 1000000
801-- stream.
801-- stream.
802--
802--
803-- >>> input = Stream.enumerateFromTo 1 3
803-- >>> 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
805-- 1
805-- 1
806-- 2
806-- 2
807-- 3
807-- 3
core/src/Streamly/Internal/Data/Stream/Type.hs+19−3
@@ -18,6 +18,7 @@ module Streamly.Internal.Data.Stream.Type
18 , toStreamK
18 , toStreamK
19 , fromStreamD
19 , fromStreamD
20 , toStreamD
20 , toStreamD
21+ , Streamly.Internal.Data.Stream.Type.fromList
21
22
22 -- * Construction
23 -- * Construction
23 , cons
24 , cons
@@ -122,6 +123,20 @@ fromStreamD = fromStreamK . D.toStreamK
122toStreamD :: Applicative m => Stream m a -> D.Stream m a
123toStreamD :: Applicative m => Stream m a -> D.Stream m a
123toStreamD = D.fromStreamK . toStreamK
124toStreamD = D.fromStreamK . toStreamK
124
125
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+
125------------------------------------------------------------------------------
140------------------------------------------------------------------------------
126-- Comparison
141-- Comparison
127------------------------------------------------------------------------------
142------------------------------------------------------------------------------
@@ -320,7 +335,7 @@ instance Show a => Show (Stream Identity a) where
320instance Read a => Read (Stream Identity a) where
335instance Read a => Read (Stream Identity a) where
321 readPrec = parens $ prec 10 $ do
336 readPrec = parens $ prec 10 $ do
322 Ident "fromList" <- lexP
337 Ident "fromList" <- lexP
323− fromList <$> readPrec
338+ Streamly.Internal.Data.Stream.Type.fromList <$> readPrec
324
339
325 readListPrec = readListPrecDefault
340 readListPrec = readListPrecDefault
326
341
@@ -450,7 +465,7 @@ infixr 5 `consM`
450consM :: Monad m => m a -> Stream m a -> Stream m a
465consM :: Monad m => m a -> Stream m a -> Stream m a
451consM m = fromStreamK . K.consM m . toStreamK
466consM m = fromStreamK . K.consM m . toStreamK
452
467
453−-- | A pure empty stream with no result and no side-effect.
468+-- | A stream that terminates without producing any output or side effect.
454--
469--
455-- >>> Stream.fold Fold.toList Stream.nil
470-- >>> Stream.fold Fold.toList Stream.nil
456-- []
471-- []
@@ -459,7 +474,8 @@ consM m = fromStreamK . K.consM m . toStreamK
459nil :: Stream m a
474nil :: Stream m a
460nil = fromStreamK K.nil
475nil = fromStreamK K.nil
461
476
462−-- | An empty stream producing the supplied side effect.
477+-- | A stream that terminates without producing any output, but produces a side
478+-- effect.
463--
479--
464-- >>> Stream.fold Fold.toList (Stream.nilM (print "nil"))
480-- >>> Stream.fold Fold.toList (Stream.nilM (print "nil"))
465-- "nil"
481-- "nil"
core/src/Streamly/Internal/FileSystem/File.hs+2−2
@@ -114,7 +114,7 @@ import Streamly.Internal.System.IO (defaultChunkSize)
114import qualified Streamly.Data.Array.Unboxed as A
114import qualified Streamly.Data.Array.Unboxed as A
115import qualified Streamly.Data.Unfold as UF
115import qualified Streamly.Data.Unfold as UF
116import qualified Streamly.Internal.Data.Fold.Type as FL
116import qualified Streamly.Internal.Data.Fold.Type as FL
117− (Step(..), snoc, initialize)
117+ (Step(..), snoc, reduce)
118import qualified Streamly.Internal.Data.Unfold as UF (bracketIO)
118import qualified Streamly.Internal.Data.Unfold as UF (bracketIO)
119import qualified Streamly.Internal.FileSystem.Handle as FH
119import qualified Streamly.Internal.FileSystem.Handle as FH
120import qualified Streamly.Internal.Data.Array.Unboxed.Stream as AS
120import qualified Streamly.Internal.Data.Array.Unboxed.Stream as AS
@@ -432,7 +432,7 @@ writeChunks path = Fold step initial extract
432 where
432 where
433 initial = do
433 initial = do
434 h <- liftIO (openFile path WriteMode)
434 h <- liftIO (openFile path WriteMode)
435− fld <- FL.initialize (FH.writeChunks h)
435+ fld <- FL.reduce (FH.writeChunks h)
436 `MC.onException` liftIO (hClose h)
436 `MC.onException` liftIO (hClose h)
437 return $ FL.Partial (fld, h)
437 return $ FL.Partial (fld, h)
438 step (fld, h) x = do
438 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 ->
405--
405--
406{-# INLINE putChunks #-}
406{-# INLINE putChunks #-}
407putChunks :: MonadIO m => Handle -> Stream m (Array a) -> m ()
407putChunks :: 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))
409
409
410-- XXX AS.compact can be written idiomatically in terms of foldMany, just like
410-- XXX AS.compact can be written idiomatically in terms of foldMany, just like
411-- AS.concat is written in terms of foldMany. Once that is done we can write
411-- AS.concat is written in terms of foldMany. Once that is done we can write
@@ -452,7 +452,7 @@ putBytes = putBytesWith defaultChunkSize
452--
452--
453{-# INLINE writeChunks #-}
453{-# INLINE writeChunks #-}
454writeChunks :: MonadIO m => Handle -> Fold m (Array a) ()
454writeChunks :: MonadIO m => Handle -> Fold m (Array a) ()
455−writeChunks h = FL.drainBy (putChunk h)
455+writeChunks h = FL.drainMapM (putChunk h)
456
456
457-- | Like writeChunks but uses the experimental 'Refold' API.
457-- | Like writeChunks but uses the experimental 'Refold' API.
458--
458--
core/streamly-core.cabal+12−3
@@ -239,7 +239,6 @@ library
239 , Streamly.Internal.Control.ForkIO
239 , Streamly.Internal.Control.ForkIO
240 , Streamly.Internal.Data.Cont
240 , Streamly.Internal.Data.Cont
241 , Streamly.Internal.System.IO
241 , Streamly.Internal.System.IO
242− , Streamly.Internal.Data.IsMap
243
242
244 -- streamly-strict-data
243 -- streamly-strict-data
245 , Streamly.Internal.Data.Tuple.Strict
244 , Streamly.Internal.Data.Tuple.Strict
@@ -397,6 +396,11 @@ library
397 , Streamly.Internal.Data.Stream.StreamDK.Type
396 , Streamly.Internal.Data.Stream.StreamDK.Type
398
397
399 build-depends:
398 build-depends:
399+ -- streamly-base
400+ --
401+ -- These dependencies can be reversed if we want
402+ -- streamly-base to depend only on base.
403+ --
400 -- Core libraries shipped with ghc, the min and max
404 -- Core libraries shipped with ghc, the min and max
401 -- constraints of these libraries should match with
405 -- constraints of these libraries should match with
402 -- the GHC versions we support. This is to make sure that
406 -- the GHC versions we support. This is to make sure that
@@ -404,18 +408,23 @@ library
404 -- depending on doctest is a common example) can
408 -- depending on doctest is a common example) can
405 -- depend on streamly.
409 -- depend on streamly.
406 base >= 4.12 && < 4.18
410 base >= 4.12 && < 4.18
407− , containers >= 0.6.0 && < 0.7
408 , deepseq >= 1.4.4 && < 1.5
411 , deepseq >= 1.4.4 && < 1.5
409− , directory >= 1.3.3 && < 1.4
410 , exceptions >= 0.8.0 && < 0.11
412 , exceptions >= 0.8.0 && < 0.11
411 , ghc-prim >= 0.5.3 && < 0.10
413 , ghc-prim >= 0.5.3 && < 0.10
412 , mtl >= 2.2.2 && < 2.4
414 , mtl >= 2.2.2 && < 2.4
413 , transformers >= 0.5.5 && < 0.7
415 , transformers >= 0.5.5 && < 0.7
416+
417+ -- streamly-unicode-core
414 , template-haskell >= 2.14 && < 2.20
418 , template-haskell >= 2.14 && < 2.20
415
419
420+ -- streamly-filesystem-core
421+ , directory >= 1.3.3 && < 1.4
422+
423+ -- fusion-plugin
416 , fusion-plugin-types >= 0.1 && < 0.2
424 , fusion-plugin-types >= 0.1 && < 0.2
417
425
418 -- XXX to be removed
426 -- XXX to be removed
427+ , containers >= 0.6.0 && < 0.7
419 , heaps >= 0.3 && < 0.5
428 , heaps >= 0.3 && < 0.5
420 if !flag(use-unliftio)
429 if !flag(use-unliftio)
421 build-depends: monad-control >= 1.0 && < 1.1
430 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
12 , bracket
12 , bracket
13 , bracket3
13 , bracket3
14 , finally
14 , finally
15+ , retry
15
16
16 -- For IsStream module
17 -- For IsStream module
17 , afterD
18 , afterD
18 , bracket3D
19 , bracket3D
20+ , retryD
19 )
21 )
20where
22where
21
23
22#include "inline.hs"
24#include "inline.hs"
23
25
24−import Control.Exception (SomeException, mask_)
26+import Control.Exception (Exception, SomeException, mask_)
25import Control.Monad.Catch (MonadCatch)
27import Control.Monad.Catch (MonadCatch)
28+import Data.Map.Strict (Map)
26import GHC.Exts (inline)
29import GHC.Exts (inline)
27import Streamly.Internal.Control.Concurrent
30import Streamly.Internal.Control.Concurrent
28 (MonadRunInIO, MonadAsync, withRunInIO)
31 (MonadRunInIO, MonadAsync, withRunInIO)
29import Streamly.Internal.Data.Stream.Type (Stream, fromStreamD, toStreamD)
32import Streamly.Internal.Data.Stream.Type (Stream, fromStreamD, toStreamD)
30import Streamly.Internal.Data.IOFinalizer.Lifted
33import Streamly.Internal.Data.IOFinalizer.Lifted
31 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
34 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
35+import Streamly.Internal.Data.Stream.StreamD (Step(..))
32
36
33import qualified Control.Monad.Catch as MC
37import qualified Control.Monad.Catch as MC
38+import qualified Data.Map.Strict as Map
34import qualified Streamly.Internal.Data.Stream.StreamD as D
39import qualified Streamly.Internal.Data.Stream.StreamD as D
35
40
36−import Streamly.Internal.Data.Stream.StreamD.Type hiding (Stream)
37−
38-- $setup
41-- $setup
39-- >>> :m
42-- >>> :m
40-- >>> import qualified Streamly.Internal.Data.Stream.Exception.Lifted as Stream
43-- >>> import qualified Streamly.Internal.Data.Stream.Exception.Lifted as Stream
@@ -76,7 +79,7 @@ gbracket bef aft onExc onGC ftry action =
76 return (r, ref)
79 return (r, ref)
77 return $ Skip $ GBracketIONormal (action r) r ref
80 return $ Skip $ GBracketIONormal (action r) r ref
78
81
79− step gst (GBracketIONormal (UnStream step1 st) v ref) = do
82+ step gst (GBracketIONormal (D.UnStream step1 st) v ref) = do
80 res <- ftry $ step1 gst st
83 res <- ftry $ step1 gst st
81 case res of
84 case res of
82 Right r -> case r of
85 Right r -> case r of
@@ -93,9 +96,9 @@ gbracket bef aft onExc onGC ftry action =
93 -- the finalizer and have not run the exception handler then we
96 -- the finalizer and have not run the exception handler then we
94 -- may leak the resource.
97 -- may leak the resource.
95 stream <-
98 stream <-
96− clearingIOFinalizer ref (onExc v e (UnStream step1 st))
99+ clearingIOFinalizer ref (onExc v e (D.UnStream step1 st))
97 return $ Skip (GBracketIOException stream)
100 return $ Skip (GBracketIOException stream)
98− step gst (GBracketIOException (UnStream step1 st)) = do
101+ step gst (GBracketIOException (D.UnStream step1 st)) = do
99 res <- step1 gst st
102 res <- step1 gst st
100 case res of
103 case res of
101 Yield x s ->
104 Yield x s ->
@@ -115,7 +118,7 @@ bracket3D bef aft onExc onGC =
115 gbracket
118 gbracket
116 bef
119 bef
117 aft
120 aft
118− (\a (e :: SomeException) _ -> onExc a >> return (nilM (MC.throwM e)))
121+ (\a (e :: SomeException) _ -> onExc a >> return (D.nilM (MC.throwM e)))
119 onGC
122 onGC
120 (inline MC.try)
123 (inline MC.try)
121
124
@@ -222,3 +225,97 @@ afterD action (D.Stream step state) = D.Stream step' Nothing
222{-# INLINE after #-}
225{-# INLINE after #-}
223after :: MonadRunInIO m => m b -> Stream m a -> Stream m a
226after :: MonadRunInIO m => m b -> Stream m a -> Stream m a
224after action xs = fromStreamD $ afterD action $ toStreamD xs
227after 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
280--
280--
281-- >>> concatFold = Prelude.foldl Stream.foldContinue Fold.sum
281-- >>> concatFold = Prelude.foldl Stream.foldContinue Fold.sum
282--
282--
283−-- >>> fold f = Fold.finish . Stream.foldContinue f
283+-- >>> fold f = Fold.extractM . Stream.foldContinue f
284--
284--
285-- /Internal/
285-- /Internal/
286{-# INLINE foldContinue #-}
286{-# 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
40 , finally_
40 , finally_
41 , ghandle
41 , ghandle
42 , handle
42 , handle
43− , retry
44 )
43 )
45
44
46import qualified Streamly.Internal.Data.Stream.Exception.Lifted as D
45import qualified Streamly.Internal.Data.Stream.Exception.Lifted as D
47 ( afterD
46 ( afterD
48 , bracket3D
47 , bracket3D
48+ , retryD
49 )
49 )
50
50
51
51
@@ -281,4 +281,4 @@ retry :: (IsStream t, MonadCatch m, Exception e, Ord e)
281 -> t m a
281 -> t m a
282 -> t m a
282 -> t m a
283retry emap handler inp =
283retry 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
709-- session is created for that key.
709-- session is created for that key.
710--
710--
711-- >>> :{
711-- >>> :{
712−-- Stream.fold (Fold.drainBy print)
712+-- Stream.fold (Fold.drainMapM print)
713-- $ Concur.classifySessionsBy 1 False (const (return False)) 3 (Fold.take 3 Fold.toList)
713-- $ Concur.classifySessionsBy 1 False (const (return False)) 3 (Fold.take 3 Fold.toList)
714-- $ Stream.timestamped
714-- $ Stream.timestamped
715-- $ Stream.delay 0.1
715-- $ Stream.delay 0.1
@@ -815,7 +815,7 @@ classifySessionsOf = classifySessionsBy 1 False
815--
815--
816{-# INLINE sampleIntervalEnd #-}
816{-# INLINE sampleIntervalEnd #-}
817sampleIntervalEnd :: MonadAsync m => Double -> Stream m a -> Stream m a
817sampleIntervalEnd :: 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
819
819
820-- | Like 'sampleInterval' but samples at the beginning of the time window.
820-- | Like 'sampleInterval' but samples at the beginning of the time window.
821--
821--
src/Streamly/Internal/FileSystem/Event/Linux.hs+1−1
@@ -713,7 +713,7 @@ addToWatch cfg@Config{..} watch0@(Watch handle wdMap) root0 path0 = do
713 pathIsDir <- doesDirectoryExist $ utf8ToString absPath
713 pathIsDir <- doesDirectoryExist $ utf8ToString absPath
714 when (watchRec && pathIsDir) $ do
714 when (watchRec && pathIsDir) $ do
715 let f = addToWatch cfg watch0 root . appendPaths path
715 let f = addToWatch cfg watch0 root . appendPaths path
716− in S.fold (FL.drainBy f)
716+ in S.fold (FL.drainMapM f)
717 $ S.mapM toUtf8
717 $ S.mapM toUtf8
718 $ Dir.readDirs $ utf8ToString absPath
718 $ Dir.readDirs $ utf8ToString absPath
719
719
src/Streamly/Internal/FileSystem/Event/Windows.hs+1−1
@@ -436,7 +436,7 @@ utf8ToStringList = NonEmpty.map utf8ToString
436closePathHandleStream :: Stream IO (HANDLE, FilePath, Config) -> IO ()
436closePathHandleStream :: Stream IO (HANDLE, FilePath, Config) -> IO ()
437closePathHandleStream =
437closePathHandleStream =
438 let f (h, _, _) = closeHandle h
438 let f (h, _, _) = closeHandle h
439− in S.fold (Fold.drainBy f)
439+ in S.fold (Fold.drainMapM f)
440
440
441-- XXX
441-- XXX
442-- Document the path treatment for Linux/Windows/macOS modules.
442-- 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−
3-- |
1-- |
4-- Module : Streamly.Internal.Network.Inet.TCP
2-- Module : Streamly.Internal.Network.Inet.TCP
5-- Copyright : (c) 2019 Composewell Technologies
3-- Copyright : (c) 2019 Composewell Technologies
@@ -94,6 +92,8 @@ module Streamly.Internal.Network.Inet.TCP
94 )
92 )
95where
93where
96
94
95+#include "inline.hs"
96+
97import Control.Exception (onException)
97import Control.Exception (onException)
98import Control.Monad.Catch (MonadCatch, MonadMask, bracket)
98import Control.Monad.Catch (MonadCatch, MonadMask, bracket)
99import Control.Monad.IO.Class (MonadIO(..))
99import Control.Monad.IO.Class (MonadIO(..))
@@ -122,8 +122,7 @@ import qualified Streamly.Data.Fold as FL
122import qualified Streamly.Data.Unfold as UF
122import qualified Streamly.Data.Unfold as UF
123import qualified Streamly.Internal.Data.Unfold as UF (bracketIO, first)
123import qualified Streamly.Internal.Data.Unfold as UF (bracketIO, first)
124import qualified Streamly.Internal.Data.Array.Unboxed.Stream as AS
124import qualified Streamly.Internal.Data.Array.Unboxed.Stream as AS
125−import qualified Streamly.Internal.Data.Fold.Type as FL
125+import qualified Streamly.Internal.Data.Fold.Type as FL (Step(..))
126− (initialize, snoc, Step(..))
127import qualified Streamly.Internal.Data.Stream as S
126import qualified Streamly.Internal.Data.Stream as S
128import qualified Streamly.Internal.Data.Stream.Exception.Lifted as S
127import qualified Streamly.Internal.Data.Stream.Exception.Lifted as S
129import qualified Streamly.Internal.Network.Socket as ISK
128import qualified Streamly.Internal.Network.Socket as ISK
@@ -355,7 +354,7 @@ writeChunks addr port = Fold step initial extract
355 where
354 where
356 initial = do
355 initial = do
357 skt <- liftIO (connect addr port)
356 skt <- liftIO (connect addr port)
358− fld <- FL.initialize (ISK.writeChunks skt)
357+ fld <- FL.reduce (ISK.writeChunks skt)
359 `MC.onException` liftIO (Net.close skt)
358 `MC.onException` liftIO (Net.close skt)
360 return $ FL.Partial (Tuple' fld skt)
359 return $ FL.Partial (Tuple' fld skt)
361 step (Tuple' fld skt) x = do
360 step (Tuple' fld skt) x = do
src/Streamly/Internal/Network/Socket.hs+2−2
@@ -450,14 +450,14 @@ reader = UF.first defaultChunkSize readerWith
450{-# INLINE putChunks #-}
450{-# INLINE putChunks #-}
451putChunks :: (MonadIO m, Unbox a)
451putChunks :: (MonadIO m, Unbox a)
452 => Socket -> Stream m (Array a) -> m ()
452 => 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))
454
454
455-- | Write a stream of arrays to a socket. Each array in the stream is written
455-- | Write a stream of arrays to a socket. Each array in the stream is written
456-- to the socket as a separate IO request.
456-- to the socket as a separate IO request.
457--
457--
458{-# INLINE writeChunks #-}
458{-# INLINE writeChunks #-}
459writeChunks :: (MonadIO m, Unbox a) => Socket -> Fold m (Array a) ()
459writeChunks :: (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)
461
461
462-- | @writeChunksWith bufsize socket@ writes a stream of arrays to
462-- | @writeChunksWith bufsize socket@ writes a stream of arrays to
463-- @socket@ after coalescing the adjacent arrays in chunks of @bufsize@.
463-- @socket@ after coalescing the adjacent arrays in chunks of @bufsize@.
streamly.cabal+4−1
@@ -337,7 +337,8 @@ library
337 -- dependency order To view dependency graph:
337 -- dependency order To view dependency graph:
338 -- graphmod | dot -Tps > deps.ps
338 -- graphmod | dot -Tps > deps.ps
339
339
340− Streamly.Internal.Data.IsMap.HashMap
340+ Streamly.Internal.Data.IsMap
341+ , Streamly.Internal.Data.IsMap.HashMap
341
342
342 -- streamly-concurrent
343 -- streamly-concurrent
343 , Streamly.Internal.Control.Concurrent
344 , Streamly.Internal.Control.Concurrent
@@ -352,6 +353,7 @@ library
352 , Streamly.Internal.Data.Fold.Concurrent.Channel.Type
353 , Streamly.Internal.Data.Fold.Concurrent.Channel.Type
353 , Streamly.Internal.Data.Fold.Concurrent.Channel
354 , Streamly.Internal.Data.Fold.Concurrent.Channel
354 , Streamly.Internal.Data.Fold.Concurrent
355 , Streamly.Internal.Data.Fold.Concurrent
356+ , Streamly.Internal.Data.Fold.Extra
355
357
356 , Streamly.Internal.Data.Stream.Concurrent.Channel.Type
358 , Streamly.Internal.Data.Stream.Concurrent.Channel.Type
357 , Streamly.Internal.Data.Stream.Concurrent.Channel.Dispatcher
359 , Streamly.Internal.Data.Stream.Concurrent.Channel.Dispatcher
@@ -364,6 +366,7 @@ library
364 , Streamly.Internal.Data.Stream.Zip.Concurrent
366 , Streamly.Internal.Data.Stream.Zip.Concurrent
365 , Streamly.Internal.Data.Stream.Time
367 , Streamly.Internal.Data.Stream.Time
366 , Streamly.Internal.Data.Stream.Exception.Lifted
368 , Streamly.Internal.Data.Stream.Exception.Lifted
369+ , Streamly.Internal.Data.Stream.Extra
367
370
368 -- streamly-unicode (depends on unicode-data)
371 -- streamly-unicode (depends on unicode-data)
369 , Streamly.Internal.Unicode.Utf8
372 , Streamly.Internal.Unicode.Utf8
test/Streamly/Test/Data/Fold.hs+1
@@ -25,6 +25,7 @@ import qualified Data.Map
25import qualified Prelude
25import qualified Prelude
26import qualified Streamly.Internal.Data.Array.Unboxed.Mut as MArray
26import qualified Streamly.Internal.Data.Array.Unboxed.Mut as MArray
27import qualified Streamly.Internal.Data.Fold as Fold
27import qualified Streamly.Internal.Data.Fold as Fold
28+import qualified Streamly.Internal.Data.Fold.Extra as Fold
28import qualified Streamly.Internal.Data.Stream as Stream
29import qualified Streamly.Internal.Data.Stream as Stream
29
30
30import Prelude hiding
31import Prelude hiding