{-# LANGUAGE CPP #-} -- | -- Module : Streamly.Data.Fold -- Copyright : (c) 2019 Composewell Technologies -- License : BSD-3-Clause -- Maintainer : streamly@composewell.com -- Stability : released -- Portability : GHC -- -- Fast, composable stream consumers with ability to terminate, supporting -- stream fusion. -- -- == Using Folds -- -- This module provides elementary folds and fold combinators that can be used -- to consume a stream of data and reduce it to a final value, or transform it -- in a stateful manner using scans. A data stream can be reduced into a stream -- of folded data elements by folding segments of the stream. Fold combinators -- can be used to compose multiple folds in parallel or to create a pipeline of -- folds such that the next fold consumes the result of the previous fold. To -- run these folds on a stream see 'Streamly.Data.Stream.fold', -- 'Streamly.Data.Stream.scan', 'Streamly.Data.Stream.postscan', -- 'Streamly.Data.Stream.scanMaybe', 'Streamly.Data.Stream.foldMany' and other -- operations accepting 'Fold' type as argument "Streamly.Data.Stream". -- -- == Reducing a Stream -- -- A 'Fold' is a consumer of a stream of values. A fold driver (such as -- 'Streamly.Data.Stream.fold') initializes the fold @accumulator@, runs the -- fold @step@ function in a loop, processing the input stream one element at a -- time and accumulating the result. The loop continues until the fold -- terminates, at which point the accumulated result is returned. -- -- For example, a 'sum' Fold represents a stream consumer that adds the values -- in the input stream: -- -- >>> Stream.fold Fold.sum $ Stream.fromList [1..100] -- 5050 -- -- Conceptually, a 'Fold' is a data type that mimics a strict left fold -- ('Data.List.foldl'). The above example is similar to a left fold using -- @(+)@ as the step and @0@ as the initial value of the accumulator: -- -- >>> Data.List.foldl' (+) 0 [1..100] -- 5050 -- -- 'Fold's have an early termination capability e.g. the 'one' fold terminates -- after consuming one element: -- -- >>> Stream.fold Fold.one $ Stream.fromList [1..] -- Just 1 -- -- The above example is similar to the following right fold: -- -- >>> Prelude.foldr (\x _ -> Just x) Nothing [1..] -- Just 1 -- -- 'Fold's can be combined together using combinators. For example, to create a -- fold that sums first two elements in a stream: -- -- >>> sumTwo = Fold.take 2 Fold.sum -- >>> Stream.fold sumTwo $ Stream.fromList [1..100] -- 3 -- -- == Parallel Composition -- -- Folds can be combined to run in parallel on the same input. For example, to -- compute the average of numbers in a stream without going through the stream -- twice: -- -- >>> avg = Fold.teeWith (/) Fold.sum (fmap fromIntegral Fold.length) -- >>> Stream.fold avg $ Stream.fromList [1.0..100.0] -- 50.5 -- -- Folds can be combined so as to partition the input stream over multiple -- folds. For example, to count even and odd numbers in a stream: -- -- >>> split n = if even n then Left n else Right n -- >>> stream = fmap split $ Stream.fromList [1..100] -- >>> countEven = fmap (("Even " ++) . show) Fold.length -- >>> countOdd = fmap (("Odd " ++) . show) Fold.length -- >>> f = Fold.partition countEven countOdd -- >>> Stream.fold f stream -- ("Even 50","Odd 50") -- -- == Sequential Composition -- -- Terminating folds can be combined to parse the stream serially such that the -- first fold consumes the input until it terminates and the second fold -- consumes the rest of the input until it terminates: -- -- >>> f = Fold.splitWith (,) (Fold.take 8 Fold.toList) (Fold.takeEndBy (== '\n') Fold.toList) -- >>> Stream.fold f $ Stream.fromList "header: hello\n" -- ("header: ","hello\n") -- -- == Splitting a Stream -- -- A 'Fold' can be applied repeatedly on a stream to transform it to a stream -- of fold results. To split a stream on newlines: -- -- >>> f = Fold.takeEndBy (== '\n') Fold.toList -- >>> Stream.fold Fold.toList $ Stream.foldMany f $ Stream.fromList "Hello there!\nHow are you\n" -- ["Hello there!\n","How are you\n"] -- -- Similarly, we can split the input of a fold too: -- -- >>> Stream.fold (Fold.many f Fold.toList) $ Stream.fromList "Hello there!\nHow are you\n" -- ["Hello there!\n","How are you\n"] -- -- == Folds vs. Streams -- -- We can often use streams or folds to achieve the same goal. However, streams -- are more efficient in composition of producers (e.g. -- 'Data.Stream.append' or 'Data.Stream.mergeBy') whereas folds are -- more efficient in composition of consumers (e.g. 'splitWith', 'partition' -- or 'teeWith'). -- -- Streams are producers, transformations on streams happen on the output side: -- -- >>> :{ -- f stream = -- Stream.filter odd stream -- & fmap (+1) -- & Stream.fold Fold.sum -- :} -- -- >>> f $ Stream.fromList [1..100 :: Int] -- 2550 -- -- Folds are stream consumers with an input stream and an output value, stream -- transformations on folds happen on the input side: -- -- >>> :{ -- f = -- Fold.filter odd -- $ Fold.lmap (+1) -- $ Fold.sum -- :} -- -- >>> Stream.fold f $ Stream.fromList [1..100 :: Int] -- 2550 -- -- Notice the similiarity in the definition of @f@ in both cases, the only -- difference is the composition by @&@ vs @$@ and the use @lmap@ vs @map@, the -- difference is due to output vs input side transformations. -- -- == Fusion Limitations -- -- Folds support stream fusion for generating loops comparable to the speed of -- C. However, it has some limitations. For fusion to work, the folds must be -- inlined, folds must be statically known and not generated dynamically, folds -- should not be passed recursively. -- -- Another limitation is due to the quadratic complexity causing slowdown when -- too many nested compositions are used. Especially, the performance of the -- Applicative instance and splitting operations (e.g. 'splitWith') degrades -- quadratically (O(n^2)) when combined @n@ times, roughly 8 or less sequenced -- operations are fine. For these cases folds can be converted to parsers and -- then used as ParserK. -- -- == Experimental APIs -- -- Please refer to "Streamly.Internal.Data.Fold" for more functions that have -- not yet been released. module Streamly.Data.Fold ( -- * Setup -- | To execute the code examples provided in this module in ghci, please -- run the following commands first. -- -- $setup -- * Running A Fold drive -- XXX Should we have a stream returning function in fold module? -- , breakStream -- * Fold Type , Fold -- (..) , Tee (..) -- * Constructors , foldl' , foldlM' , foldl1' , foldlM1' , foldr' -- * Folds -- ** Accumulators -- | Folds that never terminate, these folds are much like strict left -- folds. 'mconcat' is the fundamental accumulator. All other accumulators -- can be expressed in terms of 'mconcat' using a suitable Monoid. Instead -- of writing folds we could write Monoids and turn them into folds. -- Monoids , sconcat , mconcat , foldMap , foldMapM -- Reducers , drain , drainMapM , length , countDistinct , countDistinctInt , frequency , sum , product , mean , rollingHash , rollingHashWithSalt -- Collectors , toList , toListRev , toSet , toIntSet , topBy -- ** Non-Empty Accumulators -- | Accumulators that do not have a default value, therefore, return -- 'Nothing' on an empty stream. , latest , maximumBy , maximum , minimumBy , minimum -- ** Filtering Scanners -- | Accumulators that are usually run as a scan using the 'scanMaybe' -- combinator. , findIndices , elemIndices , deleteBy -- , uniq , uniqBy , nub , nubInt -- ** Terminating Folds , one , null -- , satisfy -- , maybe , index , the , find , findM , lookup , findIndex , elemIndex , elem , notElem , all , any , and , or -- * Incremental builders -- | Mutable arrays ("Streamly.Data.MutArray") are basic builders. You can -- use the 'Streamly.Data.MutArray.snoc' or -- 'Streamly.Data.MutArray.writeAppend' operations to incrementally build -- mutable arrays. The 'addOne' and 'addStream' combinators can be used to -- incrementally build any type of structure using a fold, including arrays -- or a stream of arrays. -- -- Use pinned arrays if you are going to use the data for IO. -- XXX should rename to "extract". can use "Fold.drive Stream.nil" instead, -- for now. -- , extractM -- , reduce , addOne -- , snocl -- XXX Can we use something like concatEffect to implement snocM? -- , snocM -- , snoclM , addStream , duplicate -- , isClosed -- * Combinators -- | Combinators are modifiers of folds. In the type @Fold m a b@, @a@ is -- the input type and @b@ is the output type. Transformations can be -- applied either on the input side (contravariant) or on the output side -- (covariant). Therefore, combinators are of one of the following general -- shapes: -- -- * @... -> Fold m a b -> Fold m c b@ (input transformation) -- * @... -> Fold m a b -> Fold m a c@ (output transformation) -- -- The input side transformations are more interesting for folds. Most of -- the following sections describe the input transformation operations on a -- fold. When an operation makes sense on both input and output side we use -- the prefix @l@ (for left) for input side operations and the prefix @r@ -- (for right) for output side operations. -- ** Mapping on output -- | The 'Functor' instance of a fold maps on the output of the fold: -- -- >>> Stream.fold (fmap show Fold.sum) (Stream.enumerateFromTo 1 100) -- "5050" -- , rmapM -- ** Mapping on Input , lmap , lmapM -- ** Scanning and Filtering , scan , postscan , scanMaybe , filter , filterM -- -- ** Mapping Filters , mapMaybe , catMaybes , catLefts , catRights , catEithers -- ** Trimming , take -- , takeInterval , takeEndBy , takeEndBy_ -- ** Splitting , splitWith , many , groupsOf -- , intervalsOf -- ** Parallel Distribution -- | For applicative composition using distribution see -- "Streamly.Internal.Data.Fold.Tee". , teeWith --, teeWithFst --, teeWithMin , tee , distribute -- ** Partitioning -- | Direct items in the input stream to different folds using a binary -- fold selector. , partition --, partitionByM --, partitionByFstM --, partitionByMinM --, partitionBy -- ** Key-value Collectors , toMap , toMapIO , demuxToMap , demuxToMapIO -- ** Key-value Scanners , classify , classifyIO , demux , demuxIO -- ** Unzipping , unzip -- ** Nesting , concatMap -- * Transforming the Monad , morphInner -- * Deprecated , chunksOf , foldr , drainBy , last , head , sequence , mapM , variance , stdDev , serialWith ) where import Prelude hiding (Foldable(..), filter, drop, dropWhile, take, takeWhile, zipWith, map, mapM_, sequence, all, any, notElem, head, last, tail, reverse, iterate, init, and, or, lookup, (!!), scanl, scanl1, replicate, concatMap, mconcat, unzip, span, splitAt, break, mapM, maybe) import Streamly.Internal.Data.Fold #include "DocTestDataFold.hs" -------------------------------------------------------------------------------- -- Deprecated -------------------------------------------------------------------------------- {-# DEPRECATED chunksOf "Please use 'groupsOf' instead" #-} {-# INLINE chunksOf #-} chunksOf :: Monad m => Int -> Fold m a b -> Fold m b c -> Fold m a c chunksOf :: forall (m :: * -> *) a b c. Monad m => Int -> Fold m a b -> Fold m b c -> Fold m a c chunksOf = forall (m :: * -> *) a b c. Monad m => Int -> Fold m a b -> Fold m b c -> Fold m a c groupsOf