on master help sign in
Data.Fold: Update docs, expose doctest setup
commit36e3e8b38e6ee13fe529069ca411f60378a4cd43 authorHarendra Kumar <harendra@composewell.com> dateSat 11 Mar 2023, 13:08 (4 years ago) parent3cf59a6
6 files changed+180−183
core/src/DocTestDataFold.hs+28 core/src/Streamly/Data/Fold.hs+129−124 core/src/Streamly/Internal/Data/Fold.hs+6−24 core/src/Streamly/Internal/Data/Fold/Tee.hs+5−6 core/src/Streamly/Internal/Data/Fold/Type.hs+8−29 core/streamly-core.cabal+4
core/src/DocTestDataFold.hsnew file+28
@@ -0,0 +1,28 @@
1+{- $setup
2+>>> :m
3+>>> :set -XFlexibleContexts
4+>>> import Control.Monad (void)
5+>>> import qualified Data.Foldable as Foldable
6+>>> import Data.Function ((&))
7+>>> import Data.Functor.Identity (Identity, runIdentity)
8+>>> import Data.IORef (newIORef, readIORef, writeIORef)
9+>>> import Data.Maybe (fromJust, isJust)
10+>>> import Data.Monoid (Endo(..), Last(..), Sum(..))
11+
12+>>> import Streamly.Data.Array (Array)
13+>>> import Streamly.Data.Fold (Fold, Tee(..))
14+>>> import Streamly.Data.Stream (Stream)
15+
16+>>> import qualified Streamly.Data.Array as Array
17+>>> import qualified Streamly.Data.Fold as Fold
18+>>> import qualified Streamly.Data.MutArray as MutArray
19+>>> import qualified Streamly.Data.Parser as Parser
20+>>> import qualified Streamly.Data.Stream as Stream
21+>>> import qualified Streamly.Data.StreamK as StreamK
22+>>> import qualified Streamly.Data.Unfold as Unfold
23+
24+For APIs that have not been released yet.
25+
26+>>> import qualified Streamly.Internal.Data.Fold as Fold
27+>>> import qualified Streamly.Internal.Data.Fold.Window as FoldW
28+-}
core/src/Streamly/Data/Fold.hs+129−124
@@ -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
core/src/Streamly/Internal/Data/Fold.hs+6−24
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12-- |
23-- Module : Streamly.Internal.Data.Fold
34-- Copyright : (c) 2019 Composewell Technologies
@@ -348,27 +349,7 @@ import Prelude hiding
348349import Streamly.Internal.Data.Fold.Type
349350import Streamly.Internal.Data.Fold.Tee
350351
351−-- $setup
352−-- >>> :m
353−-- >>> :set -package random
354−-- >>> import Control.Monad (void)
355−-- >>> import Data.Functor.Identity (Identity, runIdentity)
356−-- >>> import Data.Maybe (isJust)
357−-- >>> import Data.Monoid (Last(..), Sum(..))
358−-- >>> import Streamly.Data.Array (Array)
359−-- >>> import Streamly.Data.Fold (Fold)
360−-- >>> import Streamly.Data.Stream (Stream)
361−-- >>> import Data.IORef (newIORef, readIORef, writeIORef)
362−-- >>> import qualified Streamly.Data.Array as Array
363−-- >>> import qualified Streamly.Data.Fold as Fold
364−-- >>> import qualified Streamly.Data.Stream as Stream
365−-- >>> import qualified Streamly.Internal.Data.Array.Mut.Type as MA
366−-- >>> import qualified Streamly.Internal.Data.Fold as Fold
367−-- >>> import qualified Streamly.Internal.Data.Fold.Type as Fold
368−-- >>> import qualified Streamly.Internal.Data.Fold.Window as FoldW
369−-- >>> import qualified Streamly.Internal.Data.Parser as Parser
370−-- >>> import qualified Streamly.Internal.Data.Unfold as Unfold
371−-- >>> import Prelude hiding (break, map, span, splitAt)
352+#include "DocTestDataFold.hs"
372353
373354------------------------------------------------------------------------------
374355-- Running
@@ -2070,6 +2051,7 @@ partitionByMUsing t f fld1 fld2 =
20702051--
20712052-- Example, send input to either fold randomly:
20722053--
2054+-- >>> :set -package random
20732055-- >>> import System.Random (randomIO)
20742056-- >>> randomly a = randomIO >>= \x -> return $ if x then Left a else Right a
20752057-- >>> f = Fold.partitionByM randomly Fold.length Fold.length
@@ -2489,7 +2471,7 @@ bottomBy cmp n = Fold step initial extract
24892471-- Example:
24902472--
24912473-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]
2492−-- >>> Stream.fold (Fold.topBy compare 3) stream >>= MA.toList
2474+-- >>> Stream.fold (Fold.topBy compare 3) stream >>= MutArray.toList
24932475-- [17,11,9]
24942476--
24952477-- /Pre-release/
@@ -2508,7 +2490,7 @@ topBy cmp = bottomBy (flip cmp)
25082490-- >>> top = Fold.topBy compare
25092491--
25102492-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]
2511−-- >>> Stream.fold (Fold.top 3) stream >>= MA.toList
2493+-- >>> Stream.fold (Fold.top 3) stream >>= MutArray.toList
25122494-- [17,11,9]
25132495--
25142496-- /Pre-release/
@@ -2523,7 +2505,7 @@ top = bottomBy $ flip compare
25232505-- >>> bottom = Fold.bottomBy compare
25242506--
25252507-- >>> stream = Stream.fromList [2::Int,7,9,3,1,5,6,11,17]
2526−-- >>> Stream.fold (Fold.bottom 3) stream >>= MA.toList
2508+-- >>> Stream.fold (Fold.bottom 3) stream >>= MutArray.toList
25272509-- [1,2,3]
25282510--
25292511-- /Pre-release/
core/src/Streamly/Internal/Data/Fold/Tee.hs+5−6
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12-- |
23-- Module : Streamly.Internal.Data.Fold.Tee
34-- Copyright : (c) 2020 Composewell Technologies
@@ -20,20 +21,18 @@ import Streamly.Internal.Data.Fold.Type (Fold)
2021
2122import qualified Streamly.Internal.Data.Fold.Type as Fold
2223
24+#include "DocTestDataFold.hs"
25+
2326-- | @Tee@ is a newtype wrapper over the 'Fold' type providing distributing
2427-- 'Applicative', 'Semigroup', 'Monoid', 'Num', 'Floating' and 'Fractional'
2528-- instances.
2629--
27−-- The input received by the
28−-- composed 'Tee' is replicated and distributed to both the constituent Tees.
30+-- The input received by the composed 'Tee' is replicated and distributed to
31+-- the constituent folds of the 'Tee'.
2932--
3033-- For example, to compute the average of numbers in a stream without going
3134-- through the stream twice:
3235--
33−-- >>> import Streamly.Internal.Data.Fold.Tee (Tee(..))
34−-- >>> import qualified Streamly.Data.Stream as Stream
35−-- >>> import qualified Streamly.Data.Fold as Fold
36−--
3736-- >>> avg = (/) <$> (Tee Fold.sum) <*> (Tee $ fmap fromIntegral Fold.length)
3837-- >>> Stream.fold (unTee avg) $ Stream.fromList [1.0..100.0]
3938-- 50.5
core/src/Streamly/Internal/Data/Fold/Type.hs+8−29
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
12-- |
23-- Module : Streamly.Internal.Data.Fold.Type
34-- Copyright : (c) 2019 Composewell Technologies
@@ -424,7 +425,7 @@ where
424425
425426#include "inline.hs"
426427
427−-- import Control.Applicative (liftA2)
428+import Control.Applicative (liftA2)
428429import Control.Monad ((>=>))
429430import Data.Bifunctor (Bifunctor(..))
430431import Data.Either (fromLeft, fromRight, isLeft, isRight)
@@ -439,21 +440,7 @@ import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
439440
440441import Prelude hiding (concatMap, filter, foldr, map, take)
441442
442−-- $setup
443−-- >>> :m
444−-- >>> :set -XFlexibleContexts
445−-- >>> import Data.Functor.Identity (runIdentity)
446−-- >>> import Data.Maybe (fromJust, isJust)
447−-- >>> import Data.Monoid (Endo(..))
448−-- >>> import Streamly.Data.Fold (Fold)
449−-- >>> import Streamly.Data.Stream (Stream)
450−-- >>> import qualified Data.Foldable as Foldable
451−-- >>> import qualified Streamly.Data.Fold as Fold
452−-- >>> import qualified Streamly.Internal.Data.Fold as Fold
453−-- >>> import qualified Streamly.Internal.Data.Fold.Type as Fold
454−-- >>> import qualified Streamly.Internal.Data.Stream as Stream
455−-- >>> import qualified Streamly.Internal.Data.Stream.StreamK as StreamK
456−-- >>> import Prelude hiding (concatMap, filter, map)
443+#include "DocTestDataFold.hs"
457444
458445------------------------------------------------------------------------------
459446-- The Fold type
@@ -474,10 +461,8 @@ import Prelude hiding (concatMap, filter, foldr, map, take)
474461-- intermediate state (see 'Step'). At any point the fold driver can extract
475462-- the result from the intermediate state using the @extract@ function.
476463--
477−-- NOTE: The constructor is not yet exposed via exposed modules, smart
478−-- constructors are provided to create folds. If you think you need the
479−-- constructor of this type please consider using the smart constructors in
480−-- "Streamly.Internal.Data.Fold" instead.
464+-- NOTE: The constructor is not yet released, smart constructors are provided
465+-- to create folds.
481466--
482467data Fold m a b =
483468 -- | @Fold @ @ step @ @ initial @ @ extract@
@@ -873,13 +858,10 @@ split_ (Fold stepL initialL _) (Fold stepR initialR extractR) =
873858 Partial sR -> extractR sR
874859 Done rR -> return rR
875860
876−-- Disabled because this uses the non-CPS style splitWith operation. We should
877−-- use a CPS style operation to be compatible with parsers. For fused
878−-- applicative we can use a separate (Split) newtype instead.
879−
880−{-
881861-- | 'Applicative' form of 'splitWith'. Split the input serially over two
882−-- folds.
862+-- folds. Note that this fuses but performance degrades quadratically with
863+-- respect to the number of compositions. It should be good to use for less
864+-- than 8 compositions.
883865instance Monad m => Applicative (Fold m a) where
884866 {-# INLINE pure #-}
885867 pure = fromPure
@@ -892,7 +874,6 @@ instance Monad m => Applicative (Fold m a) where
892874
893875 {-# INLINE liftA2 #-}
894876 liftA2 f x = (<*>) (fmap f x)
895−-}
896877
897878{-# ANN type TeeState Fuse #-}
898879data TeeState sL sR bL bR
@@ -1428,8 +1409,6 @@ take n (Fold fstep finitial fextract) = Fold step initial extract
14281409-- 'duplicate' essentially appends a stream to the fold without finishing the
14291410-- fold. Compare with 'snoc' which appends a singleton value to the fold.
14301411--
1431−-- See also 'Streamly.Internal.Data.Stream.build'.
1432−--
14331412-- /Pre-release/
14341413{-# INLINE duplicate #-}
14351414duplicate :: Monad m => Fold m a b -> Fold m a (Fold m a b)
core/streamly-core.cabal+4
@@ -37,10 +37,14 @@ extra-source-files:
3737 configure
3838 configure.ac
3939
40+ -- doctest include files
41+ src/DocTestDataFold.hs
42+
4043 -- This is duplicated
4144 src/Streamly/Internal/Data/Array/ArrayMacros.h
4245 src/assert.hs
4346 src/inline.hs
47+ src/DocTestDataArray.hs
4448
4549 src/Streamly/Internal/Data/Time/Clock/config-clock.h
4650 src/config.h.in