@@ -1,3 +1,4 @@
2-- Module : Streamly.Data.Fold
3-- Module : Streamly.Data.Fold
3-- Copyright : (c) 2019 Composewell Technologies
4-- Copyright : (c) 2019 Composewell Technologies
@@ -6,128 +7,22 @@
6-- Stability : released
7-- Stability : released
7-- Portability : GHC
8-- Portability : GHC
9−-- A 'Fold' is a sink or a consumer of a stream of values. The 'Fold' type
10+-- Composable stream consumers.
10−-- consists of an accumulator and an effectful action that absorbs a value into
13−-- >>> import Data.Function ((&))
14−-- >>> import qualified Streamly.Data.Fold as Fold
15−-- >>> import qualified Streamly.Data.Stream as Stream
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.
21−-- >>> Stream.fold Fold.sum $ Stream.fromList [1..100]
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:
29−-- >>> Data.List.foldl' (+) 0 [1..100]
32−-- 'Fold's have an early termination capability e.g. the 'one' fold would
33−-- terminate on an infinite stream:
35−-- >>> Stream.fold Fold.one $ Stream.fromList [1..]
38−-- The above example is similar to the following right fold:
40−-- >>> Prelude.foldr (\x _ -> Just x) Nothing [1..]
43−-- 'Fold's can be combined together using combinators. For example, to create a
44−-- fold that sums first two elements in a stream:
46−-- >>> sumTwo = Fold.take 2 Fold.sum
47−-- >>> Stream.fold sumTwo $ Stream.fromList [1..100]
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
54−-- >>> avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)
55−-- >>> Stream.fold avg $ Stream.fromList [1.0..100.0]
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:
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")
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:
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")
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:
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"]
84−-- Similarly, we can split the input of a fold too:
86−-- >>> Stream.fold (Fold.many f Fold.toList) $ Stream.fromList "Hello there!\nHow are you\n"
87−-- ["Hello there!\n","How are you\n"]
89−-- Please see "Streamly.Internal.Data.Fold" for additional @Pre-release@
12+-- Please refer to "Streamly.Internal.Data.Fold" for more functions that have
90−-- functions.
13+-- not yet been released.
92−-- = Folds vs. 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.
96−-- 'Data.Stream.append' or 'Data.Stream.mergeBy') whereas folds are
97−-- more efficient in composition of consumers (e.g. 'splitWith', 'partition'
100−-- Streams are producers, transformations on streams happen on the output side:
104−-- Stream.filter odd stream
106−-- & Stream.fold Fold.sum
109−-- >>> f $ Stream.fromList [1..100 :: Int]
112−-- Folds are stream consumers with an input stream and an output value, stream
113−-- transformations on folds happen on the input side:
122−-- >>> Stream.fold f $ Stream.fromList [1..100 :: Int]
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.
129module Streamly.Data.Fold
15module Streamly.Data.Fold
18+ -- | To execute the code examples provided in this module in ghci, please
19+ -- run the following commands first.
131 -- * Running A Fold
26 -- * Running A Fold
133 -- XXX Should we have a stream returning function in fold module?
28 -- XXX Should we have a stream returning function in fold module?
@@ -207,8 +102,6 @@ module Streamly.Data.Fold
207 , demuxIO
102 , demuxIO
209 -- ** Terminating Folds
104 -- ** Terminating Folds
210− -- | These are much like lazy right folds.
214 -- , satisfy
107 -- , satisfy
@@ -361,10 +254,122 @@ import Prelude
361import Streamly.Internal.Data.Fold
254import Streamly.Internal.Data.Fold
362import Streamly.Internal.Data.Fold.Container
255import Streamly.Internal.Data.Fold.Container
364−-- $setup
257+#include "DocTestDataFold.hs"
365−-- >>> import Data.Function ((&))
258+
366−-- >>> import qualified Streamly.Data.Fold as Fold
259+-- $overview
367−-- >>> import qualified Streamly.Data.Stream as Stream
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.
267+-- For example, a 'sum' Fold represents a stream consumer that adds the values
268+-- in the input stream:
270+-- >>> Stream.fold Fold.sum $ Stream.fromList [1..100]
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:
277+-- >>> Data.List.foldl' (+) 0 [1..100]
280+-- 'Fold's have an early termination capability e.g. the 'one' fold terminates
281+-- after consuming one element:
283+-- >>> Stream.fold Fold.one $ Stream.fromList [1..]
286+-- The above example is similar to the following right fold:
288+-- >>> Prelude.foldr (\x _ -> Just x) Nothing [1..]
291+-- 'Fold's can be combined together using combinators. For example, to create a
292+-- fold that sums first two elements in a stream:
294+-- >>> sumTwo = Fold.take 2 Fold.sum
295+-- >>> Stream.fold sumTwo $ Stream.fromList [1..100]
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
302+-- >>> avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length)
303+-- >>> Stream.fold avg $ Stream.fromList [1.0..100.0]
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:
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")
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:
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")
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:
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"]
332+-- Similarly, we can split the input of a fold too:
334+-- >>> Stream.fold (Fold.many f Fold.toList) $ Stream.fromList "Hello there!\nHow are you\n"
335+-- ["Hello there!\n","How are you\n"]
337+-- = Folds vs. Streams
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'
345+-- Streams are producers, transformations on streams happen on the output side:
349+-- Stream.filter odd stream
351+-- & Stream.fold Fold.sum
354+-- >>> f $ Stream.fromList [1..100 :: Int]
357+-- Folds are stream consumers with an input stream and an output value, stream
358+-- transformations on folds happen on the input side:
367+-- >>> Stream.fold f $ Stream.fromList [1..100 :: Int]
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.
369--------------------------------------------------------------------------------
374--------------------------------------------------------------------------------
370-- Deprecated
375-- Deprecated