@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12-- |
23-- Module : Streamly.Data.Fold
34-- Copyright : (c) 2019 Composewell Technologies
@@ -6,128 +7,22 @@
67-- Stability : released
78-- Portability : GHC
89--
9−-- A 'Fold' is a sink or a consumer of a stream of values. The 'Fold' type
10−-- consists of an accumulator and an effectful action that absorbs a value into
11−-- the accumulator.
10+-- Composable stream consumers.
1211--
13−-- >>> import Data.Function ((&))
14−-- >>> import qualified Streamly.Data.Fold as Fold
15−-- >>> import qualified Streamly.Data.Stream as Stream
16−--
17−-- For example, a 'sum' Fold represents adding the input to the accumulated
18−-- sum. A fold driver pushes values from a stream to the 'Fold' one at a time,
19−-- reducing the stream to a single value.
20−--
21−-- >>> Stream.fold Fold.sum $ Stream.fromList [1..100]
22−-- 5050
23−--
24−-- Conceptually, a 'Fold' is a data type that can mimic a strict left fold
25−-- ('Data.List.foldl') as well as lazy right fold ('Prelude.foldr'). The above
26−-- example is similar to a left fold using @(+)@ as the step and @0@ as the
27−-- initial value of the accumulator:
28−--
29−-- >>> Data.List.foldl' (+) 0 [1..100]
30−-- 5050
31−--
32−-- 'Fold's have an early termination capability e.g. the 'one' fold would
33−-- terminate on an infinite stream:
34−--
35−-- >>> Stream.fold Fold.one $ Stream.fromList [1..]
36−-- Just 1
37−--
38−-- The above example is similar to the following right fold:
39−--
40−-- >>> Prelude.foldr (\x _ -> Just x) Nothing [1..]
41−-- Just 1
42−--
43−-- 'Fold's can be combined together using combinators. For example, to create a
44−-- fold that sums first two elements in a stream:
45−--
46−-- >>> sumTwo = Fold.take 2 Fold.sum
47−-- >>> Stream.fold sumTwo $ Stream.fromList [1..100]
48−-- 3
49−--
50−-- Folds can be combined to run in parallel on the same input. For example, to
51−-- compute the average of numbers in a stream without going through the stream
52−-- twice:
53−--
54−-- >>> avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)
55−-- >>> Stream.fold avg $ Stream.fromList [1.0..100.0]
56−-- 50.5
57−--
58−-- Folds can be combined so as to partition the input stream over multiple
59−-- folds. For example, to count even and odd numbers in a stream:
60−--
61−-- >>> split n = if even n then Left n else Right n
62−-- >>> stream = fmap split $ Stream.fromList [1..100]
63−-- >>> countEven = fmap (("Even " ++) . show) Fold.length
64−-- >>> countOdd = fmap (("Odd " ++) . show) Fold.length
65−-- >>> f = Fold.partition countEven countOdd
66−-- >>> Stream.fold f stream
67−-- ("Even 50","Odd 50")
68−--
69−-- Terminating folds can be combined to parse the stream serially such that the
70−-- first fold consumes the input until it terminates and the second fold
71−-- consumes the rest of the input until it terminates:
72−--
73−-- >>> f = Fold.splitWith (,) (Fold.take 8 Fold.toList) (Fold.takeEndBy (== '\n') Fold.toList)
74−-- >>> Stream.fold f $ Stream.fromList "header: hello\n"
75−-- ("header: ","hello\n")
76−--
77−-- A 'Fold' can be applied repeatedly on a stream to transform it to a stream
78−-- of fold results. To split a stream on newlines:
79−--
80−-- >>> f = Fold.takeEndBy (== '\n') Fold.toList
81−-- >>> Stream.fold Fold.toList $ Stream.foldMany f $ Stream.fromList "Hello there!\nHow are you\n"
82−-- ["Hello there!\n","How are you\n"]
83−--
84−-- Similarly, we can split the input of a fold too:
85−--
86−-- >>> Stream.fold (Fold.many f Fold.toList) $ Stream.fromList "Hello there!\nHow are you\n"
87−-- ["Hello there!\n","How are you\n"]
88−--
89−-- Please see "Streamly.Internal.Data.Fold" for additional @Pre-release@
90−-- functions.
91−--
92−-- = Folds vs. Streams
93−--
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.
96−-- 'Data.Stream.append' or 'Data.Stream.mergeBy') whereas folds are
97−-- more efficient in composition of consumers (e.g. 'splitWith', 'partition'
98−-- or 'teeWith').
99−--
100−-- Streams are producers, transformations on streams happen on the output side:
101−--
102−-- >>> :{
103−-- f stream =
104−-- Stream.filter odd stream
105−-- & fmap (+1)
106−-- & Stream.fold Fold.sum
107−-- :}
108−--
109−-- >>> f $ Stream.fromList [1..100 :: Int]
110−-- 2550
111−--
112−-- Folds are stream consumers with an input stream and an output value, stream
113−-- transformations on folds happen on the input side:
114−--
115−-- >>> :{
116−-- f =
117−-- Fold.filter odd
118−-- $ Fold.lmap (+1)
119−-- $ Fold.sum
120−-- :}
121−--
122−-- >>> Stream.fold f $ Stream.fromList [1..100 :: Int]
123−-- 2550
124−--
125−-- Notice the similiarity in the definition of @f@ in both cases, the only
126−-- difference is the composition by @&@ vs @$@ and the use @lmap@ vs @map@, the
127−-- difference is due to output vs input side transformations.
12+-- Please refer to "Streamly.Internal.Data.Fold" for more functions that have
13+-- not yet been released.
12814
12915module Streamly.Data.Fold
13016 (
17+ -- * Setup
18+ -- | To execute the code examples provided in this module in ghci, please
19+ -- run the following commands first.
20+ --
21+ -- $setup
22+
23+ -- * Overview
24+ -- $overview
25+
13126 -- * Running A Fold
13227 drive
13328 -- XXX Should we have a stream returning function in fold module?
@@ -207,8 +102,6 @@ module Streamly.Data.Fold
207102 , demuxIO
208103
209104 -- ** Terminating Folds
210− -- | These are much like lazy right folds.
211−
212105 , one
213106 , null
214107 -- , satisfy
@@ -361,10 +254,122 @@ import Prelude
361254import Streamly.Internal.Data.Fold
362255import Streamly.Internal.Data.Fold.Container
363256
364−-- $setup
365−-- >>> import Data.Function ((&))
366−-- >>> import qualified Streamly.Data.Fold as Fold
367−-- >>> import qualified Streamly.Data.Stream as Stream
257+#include "DocTestDataFold.hs"
258+
259+-- $overview
260+--
261+-- A 'Fold' is a consumer of a stream of values. A fold driver (such as
262+-- 'Streamly.Data.Stream.fold') initializes the fold @accumulator@, runs the
263+-- fold @step@ function in a loop, processing the input stream one element at a
264+-- time and accumulating the result. The loop continues until the fold
265+-- terminates, at which point the accumulated result is returned.
266+--
267+-- For example, a 'sum' Fold represents a stream consumer that adds the values
268+-- in the input stream:
269+--
270+-- >>> Stream.fold Fold.sum $ Stream.fromList [1..100]
271+-- 5050
272+--
273+-- Conceptually, a 'Fold' is a data type that mimics a strict left fold
274+-- ('Data.List.foldl'). The above example is similar to a left fold using
275+-- @(+)@ as the step and @0@ as the initial value of the accumulator:
276+--
277+-- >>> Data.List.foldl' (+) 0 [1..100]
278+-- 5050
279+--
280+-- 'Fold's have an early termination capability e.g. the 'one' fold terminates
281+-- after consuming one element:
282+--
283+-- >>> Stream.fold Fold.one $ Stream.fromList [1..]
284+-- Just 1
285+--
286+-- The above example is similar to the following right fold:
287+--
288+-- >>> Prelude.foldr (\x _ -> Just x) Nothing [1..]
289+-- Just 1
290+--
291+-- 'Fold's can be combined together using combinators. For example, to create a
292+-- fold that sums first two elements in a stream:
293+--
294+-- >>> sumTwo = Fold.take 2 Fold.sum
295+-- >>> Stream.fold sumTwo $ Stream.fromList [1..100]
296+-- 3
297+--
298+-- Folds can be combined to run in parallel on the same input. For example, to
299+-- compute the average of numbers in a stream without going through the stream
300+-- twice:
301+--
302+-- >>> avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)
303+-- >>> Stream.fold avg $ Stream.fromList [1.0..100.0]
304+-- 50.5
305+--
306+-- Folds can be combined so as to partition the input stream over multiple
307+-- folds. For example, to count even and odd numbers in a stream:
308+--
309+-- >>> split n = if even n then Left n else Right n
310+-- >>> stream = fmap split $ Stream.fromList [1..100]
311+-- >>> countEven = fmap (("Even " ++) . show) Fold.length
312+-- >>> countOdd = fmap (("Odd " ++) . show) Fold.length
313+-- >>> f = Fold.partition countEven countOdd
314+-- >>> Stream.fold f stream
315+-- ("Even 50","Odd 50")
316+--
317+-- Terminating folds can be combined to parse the stream serially such that the
318+-- first fold consumes the input until it terminates and the second fold
319+-- consumes the rest of the input until it terminates:
320+--
321+-- >>> f = Fold.splitWith (,) (Fold.take 8 Fold.toList) (Fold.takeEndBy (== '\n') Fold.toList)
322+-- >>> Stream.fold f $ Stream.fromList "header: hello\n"
323+-- ("header: ","hello\n")
324+--
325+-- A 'Fold' can be applied repeatedly on a stream to transform it to a stream
326+-- of fold results. To split a stream on newlines:
327+--
328+-- >>> f = Fold.takeEndBy (== '\n') Fold.toList
329+-- >>> Stream.fold Fold.toList $ Stream.foldMany f $ Stream.fromList "Hello there!\nHow are you\n"
330+-- ["Hello there!\n","How are you\n"]
331+--
332+-- Similarly, we can split the input of a fold too:
333+--
334+-- >>> Stream.fold (Fold.many f Fold.toList) $ Stream.fromList "Hello there!\nHow are you\n"
335+-- ["Hello there!\n","How are you\n"]
336+--
337+-- = Folds vs. Streams
338+--
339+-- We can often use streams or folds to achieve the same goal. However, streams
340+-- are more efficient in composition of producers (e.g.
341+-- 'Data.Stream.append' or 'Data.Stream.mergeBy') whereas folds are
342+-- more efficient in composition of consumers (e.g. 'splitWith', 'partition'
343+-- or 'teeWith').
344+--
345+-- Streams are producers, transformations on streams happen on the output side:
346+--
347+-- >>> :{
348+-- f stream =
349+-- Stream.filter odd stream
350+-- & fmap (+1)
351+-- & Stream.fold Fold.sum
352+-- :}
353+--
354+-- >>> f $ Stream.fromList [1..100 :: Int]
355+-- 2550
356+--
357+-- Folds are stream consumers with an input stream and an output value, stream
358+-- transformations on folds happen on the input side:
359+--
360+-- >>> :{
361+-- f =
362+-- Fold.filter odd
363+-- $ Fold.lmap (+1)
364+-- $ Fold.sum
365+-- :}
366+--
367+-- >>> Stream.fold f $ Stream.fromList [1..100 :: Int]
368+-- 2550
369+--
370+-- Notice the similiarity in the definition of @f@ in both cases, the only
371+-- difference is the composition by @&@ vs @$@ and the use @lmap@ vs @map@, the
372+-- difference is due to output vs input side transformations.
368373
369374--------------------------------------------------------------------------------
370375-- Deprecated