on d29030f help sign in
384 lines 11 kB history raw
1{-# LANGUAGE CPP #-}
2-- |
3-- Module : Streamly.Data.Fold
4-- Copyright : (c) 2019 Composewell Technologies
5-- License : BSD-3-Clause
6-- Maintainer : streamly@composewell.com
7-- Stability : released
8-- Portability : GHC
9--
10-- Fast, composable stream consumers with ability to terminate, supporting
11-- stream fusion.
12--
13-- Please refer to "Streamly.Internal.Data.Fold" for more functions that have
14-- not yet been released.
16module Streamly.Data.Fold
17 (
18 -- * Setup
19 -- | To execute the code examples provided in this module in ghci, please
20 -- run the following commands first.
21 --
22 -- $setup
24 -- * Overview
25 -- $overview
27 -- * Running A Fold
28 drive
29 -- XXX Should we have a stream returning function in fold module?
30 -- , breakStream
32 -- * Fold Type
34 , Fold -- (..)
35 , Tee (..)
37 -- * Constructors
38 , foldl'
39 , foldlM'
40 , foldl1'
41 , foldlM1'
42 , foldr'
44 -- * Folds
45 -- ** Accumulators
46 -- | Folds that never terminate, these folds are much like strict left
47 -- folds. 'mconcat' is the fundamental accumulator. All other accumulators
48 -- can be expressed in terms of 'mconcat' using a suitable Monoid. Instead
49 -- of writing folds we could write Monoids and turn them into folds.
51 -- Monoids
52 , sconcat
53 , mconcat
54 , foldMap
55 , foldMapM
57 -- Reducers
58 , drain
59 , drainMapM
60 , length
61 , countDistinct
62 , countDistinctInt
63 , frequency
64 , sum
65 , product
66 , mean
67 , rollingHash
68 , rollingHashWithSalt
70 -- Collectors
71 , toList
72 , toListRev
73 , toSet
74 , toIntSet
75 , topBy
77 -- ** Non-Empty Accumulators
78 -- | Accumulators that do not have a default value, therefore, return
79 -- 'Nothing' on an empty stream.
80 , latest
81 , maximumBy
82 , maximum
83 , minimumBy
84 , minimum
86 -- ** Filtering Scanners
87 -- | Accumulators that are usually run as a scan using the 'scanMaybe'
88 -- combinator.
89 , findIndices
90 , elemIndices
91 , deleteBy
92 -- , uniq
93 , uniqBy
94 , nub
95 , nubInt
97 -- ** Terminating Folds
98 , one
99 , null
100 -- , satisfy
101 -- , maybe
103 , index
104 , the
105 , find
106 , findM
107 , lookup
108 , findIndex
109 , elemIndex
110 , elem
111 , notElem
112 , all
113 , any
114 , and
115 , or
117 -- * Incremental builders
118 -- | Mutable arrays ("Streamly.Data.MutArray") are basic builders. You can
119 -- use the 'Streamly.Data.MutArray.snoc' or
120 -- 'Streamly.Data.MutArray.writeAppend' operations to incrementally build
121 -- mutable arrays. The 'addOne' and 'addStream' combinators can be used to
122 -- incrementally build any type of structure using a fold, including arrays
123 -- or a stream of arrays.
124 --
125 -- Use pinned arrays if you are going to use the data for IO.
127 -- XXX should rename to "extract". can use "Fold.drive Stream.nil" instead,
128 -- for now.
129 -- , extractM
130 -- , reduce
131 , addOne
132 -- , snocl
133 -- XXX Can we use something like concatEffect to implement snocM?
134 -- , snocM
135 -- , snoclM
136 , addStream
137 , duplicate
138 -- , isClosed
140 -- * Combinators
141 -- | Combinators are modifiers of folds. In the type @Fold m a b@, @a@ is
142 -- the input type and @b@ is the output type. Transformations can be
143 -- applied either on the input side (contravariant) or on the output side
144 -- (covariant). Therefore, combinators are of one of the following general
145 -- shapes:
146 --
147 -- * @... -> Fold m a b -> Fold m c b@ (input transformation)
148 -- * @... -> Fold m a b -> Fold m a c@ (output transformation)
149 --
150 -- The input side transformations are more interesting for folds. Most of
151 -- the following sections describe the input transformation operations on a
152 -- fold. When an operation makes sense on both input and output side we use
153 -- the prefix @l@ (for left) for input side operations and the prefix @r@
154 -- (for right) for output side operations.
156 -- ** Mapping on output
157 -- | The 'Functor' instance of a fold maps on the output of the fold:
158 --
159 -- >>> Stream.fold (fmap show Fold.sum) (Stream.enumerateFromTo 1 100)
160 -- "5050"
161 --
162 , rmapM
164 -- ** Mapping on Input
165 , lmap
166 , lmapM
168 -- ** Scanning and Filtering
169 , scan
170 , postscan
171 , scanMaybe
172 , filter
173 , filterM
175 -- -- ** Mapping Filters
176 , mapMaybe
177 , catMaybes
178 , catLefts
179 , catRights
180 , catEithers
182 -- ** Trimming
183 , take
184 -- , takeInterval
185 , takeEndBy
186 , takeEndBy_
188 -- ** Splitting
189 , splitWith
190 , many
191 , groupsOf
192 -- , intervalsOf
194 -- ** Parallel Distribution
195 -- | For applicative composition using distribution see
196 -- "Streamly.Internal.Data.Fold.Tee".
198 , teeWith
199 --, teeWithFst
200 --, teeWithMin
201 , tee
202 , distribute
204 -- ** Partitioning
205 -- | Direct items in the input stream to different folds using a binary
206 -- fold selector.
208 , partition
209 --, partitionByM
210 --, partitionByFstM
211 --, partitionByMinM
212 --, partitionBy
214 -- ** Key-value Collectors
215 , toMap
216 , toMapIO
217 , demuxToMap
218 , demuxToMapIO
220 -- ** Key-value Scanners
221 , classify
222 , classifyIO
223 , demux
224 , demuxIO
226 -- ** Unzipping
227 , unzip
229 -- ** Nesting
230 , concatMap
232 -- * Transforming the Monad
233 , morphInner
235 -- * Deprecated
236 , chunksOf
237 , foldr
238 , drainBy
239 , last
240 , head
241 , sequence
242 , mapM
243 , variance
244 , stdDev
245 , serialWith
246 )
247where
249import Prelude
250 hiding (Foldable(..), filter, drop, dropWhile, take, takeWhile, zipWith,
251 map, mapM_, sequence, all, any,
252 notElem, head, last, tail,
253 reverse, iterate, init, and, or, lookup, (!!),
254 scanl, scanl1, replicate, concatMap, mconcat, unzip,
255 span, splitAt, break, mapM, maybe)
257import Streamly.Internal.Data.Fold
258import Streamly.Internal.Data.Fold.Container
260#include "DocTestDataFold.hs"
262-- $overview
263--
264-- A 'Fold' is a consumer of a stream of values. A fold driver (such as
265-- 'Streamly.Data.Stream.fold') initializes the fold @accumulator@, runs the
266-- fold @step@ function in a loop, processing the input stream one element at a
267-- time and accumulating the result. The loop continues until the fold
268-- terminates, at which point the accumulated result is returned.
269--
270-- For example, a 'sum' Fold represents a stream consumer that adds the values
271-- in the input stream:
272--
273-- >>> Stream.fold Fold.sum $ Stream.fromList [1..100]
274-- 5050
275--
276-- Conceptually, a 'Fold' is a data type that mimics a strict left fold
277-- ('Data.List.foldl'). The above example is similar to a left fold using
278-- @(+)@ as the step and @0@ as the initial value of the accumulator:
279--
280-- >>> Data.List.foldl' (+) 0 [1..100]
281-- 5050
282--
283-- 'Fold's have an early termination capability e.g. the 'one' fold terminates
284-- after consuming one element:
285--
286-- >>> Stream.fold Fold.one $ Stream.fromList [1..]
287-- Just 1
288--
289-- The above example is similar to the following right fold:
290--
291-- >>> Prelude.foldr (\x _ -> Just x) Nothing [1..]
292-- Just 1
293--
294-- 'Fold's can be combined together using combinators. For example, to create a
295-- fold that sums first two elements in a stream:
296--
297-- >>> sumTwo = Fold.take 2 Fold.sum
298-- >>> Stream.fold sumTwo $ Stream.fromList [1..100]
299-- 3
300--
301-- Folds can be combined to run in parallel on the same input. For example, to
302-- compute the average of numbers in a stream without going through the stream
303-- twice:
304--
305-- >>> avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)
306-- >>> Stream.fold avg $ Stream.fromList [1.0..100.0]
307-- 50.5
308--
309-- Folds can be combined so as to partition the input stream over multiple
310-- folds. For example, to count even and odd numbers in a stream:
311--
312-- >>> split n = if even n then Left n else Right n
313-- >>> stream = fmap split $ Stream.fromList [1..100]
314-- >>> countEven = fmap (("Even " ++) . show) Fold.length
315-- >>> countOdd = fmap (("Odd " ++) . show) Fold.length
316-- >>> f = Fold.partition countEven countOdd
317-- >>> Stream.fold f stream
318-- ("Even 50","Odd 50")
319--
320-- Terminating folds can be combined to parse the stream serially such that the
321-- first fold consumes the input until it terminates and the second fold
322-- consumes the rest of the input until it terminates:
323--
324-- >>> f = Fold.splitWith (,) (Fold.take 8 Fold.toList) (Fold.takeEndBy (== '\n') Fold.toList)
325-- >>> Stream.fold f $ Stream.fromList "header: hello\n"
326-- ("header: ","hello\n")
327--
328-- A 'Fold' can be applied repeatedly on a stream to transform it to a stream
329-- of fold results. To split a stream on newlines:
330--
331-- >>> f = Fold.takeEndBy (== '\n') Fold.toList
332-- >>> Stream.fold Fold.toList $ Stream.foldMany f $ Stream.fromList "Hello there!\nHow are you\n"
333-- ["Hello there!\n","How are you\n"]
334--
335-- Similarly, we can split the input of a fold too:
336--
337-- >>> Stream.fold (Fold.many f Fold.toList) $ Stream.fromList "Hello there!\nHow are you\n"
338-- ["Hello there!\n","How are you\n"]
339--
340-- = Folds vs. Streams
341--
342-- We can often use streams or folds to achieve the same goal. However, streams
343-- are more efficient in composition of producers (e.g.
344-- 'Data.Stream.append' or 'Data.Stream.mergeBy') whereas folds are
345-- more efficient in composition of consumers (e.g. 'splitWith', 'partition'
346-- or 'teeWith').
347--
348-- Streams are producers, transformations on streams happen on the output side:
349--
350-- >>> :{
351-- f stream =
352-- Stream.filter odd stream
353-- & fmap (+1)
354-- & Stream.fold Fold.sum
355-- :}
356--
357-- >>> f $ Stream.fromList [1..100 :: Int]
358-- 2550
359--
360-- Folds are stream consumers with an input stream and an output value, stream
361-- transformations on folds happen on the input side:
362--
363-- >>> :{
364-- f =
365-- Fold.filter odd
366-- $ Fold.lmap (+1)
367-- $ Fold.sum
368-- :}
369--
370-- >>> Stream.fold f $ Stream.fromList [1..100 :: Int]
371-- 2550
372--
373-- Notice the similiarity in the definition of @f@ in both cases, the only
374-- difference is the composition by @&@ vs @$@ and the use @lmap@ vs @map@, the
375-- difference is due to output vs input side transformations.
377--------------------------------------------------------------------------------
378-- Deprecated
379--------------------------------------------------------------------------------
381{-# DEPRECATED chunksOf "Please use 'groupsOf' instead" #-}
382{-# INLINE chunksOf #-}
383chunksOf :: Monad m => Int -> Fold m a b -> Fold m b c -> Fold m a c
384chunksOf = groupsOf