The Fold type represents a consumer of a sequence of values, the
corresponding dual type is Streamly.Data.Stream.Stream which represents a
producer. Both types can perform equivalent transformations on a stream. But
only Fold can be used to compose multiple consumers and only Stream can
be used to compose multiple producers.
The Fold type represents stream consumers as state machines, that fuse
together when composed statically, eliminating function calls or
intermediate constructor allocations. Stream fusion helps generate tight,
efficient loops similar to the code generated by low-level languages like C.
Folds are suitable for high-performance looping operations.
Operations in this module are designed to be composed statically rather than
dynamically. They are inlined to enable static fusion. More importantly,
they are not designed to be used recursively. Recursive use will break
fusion and will lead to quadratic performance slowdown. For dynamic or
recursive composition use the continuation passing style (CPS) operations
from the Streamly.Data.ParserK module. Fold and
Streamly.Data.ParserK.ParserK types are interconvertible via the
Streamly.Data.Parser.Parser type.
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:
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:
Example1 expression
>>> Data.List.foldl' (+) 0 [1..100]5050
Folds have an early termination capability e.g. the one fold terminates
after consuming one element:
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:
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:
Example6 expressions
>>> 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:
Example2 expressions
>>> 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:
Example2 expressions
>>> 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:
Example1 expression
>>> 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 required for composition of producers (e.g.
Data.Stream.append or Data.Stream.mergeBy) whereas folds are
required for composition of consumers (e.g. splitWith, partition
or teeWith).
Streams are producers, transformations on streams happen on the output side:
>>> 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.
The type Fold m a b represents a consumer of an input stream of values
of type a and returning a final value of type b in Monadm. The
constructor of a fold is Fold step initial extract final.
The fold uses an internal state of type s. The initial value of the state
s is created by initial. This function is called once and only once
before the fold starts consuming input. Any resource allocation can be done
in this function.
The step function is called on each input, it consumes an input and
returns the next intermediate state (see Step) or the final result b if
the fold terminates.
Folds are no longer used for scanning, please see the Streamly.Data.Scanl
module for scanning. The extract operation is no longer used and will be
removed in future.
Before a fold terminates, final is called once and only once (unless the
fold terminated in initial itself). Any resources allocated by initial
can be released in final.
When implementing fold combinators, care should be taken to cleanup any
state of the argument folds held by the fold by calling the respective
final at all exit points of the fold. Also, final should not be called
more than once. Note that if a fold terminates by Done constructor, there
is no state to cleanup.
NOTE: The constructor is not yet released, smart constructors are provided
to create folds.
Instances2Functor, Applicative
Functorm => Functor (Foldma)Defined in streamly-core-0.3.1 · Streamly.Internal.Data.Fold.Type
Maps a function on the output of the fold (the type b).
Monadm => Applicative (Foldma)Defined in streamly-core-0.3.1 · Streamly.Internal.Data.Fold.Type
Applicative form of splitWith. Split the input serially over two
folds. Note that this fuses but performance degrades quadratically with
respect to the number of compositions. It should be good to use for less
than 8 compositions.
Similarly, the Semigroup and Monoid instances of Tee distribute the
input to both the folds and combine the outputs using Monoid or Semigroup
instances of the output types:
Example3 expressions
>>> import Data.Monoid (Sum(..))>>> t = Tee Fold.one <> Tee Fold.latest>>> Stream.fold (unTee t) (fmap Sum $ Stream.enumerateFromTo 1.0 100.0)Just (Sum {getSum = 101.0})
Binary Fractional operations distribute the input to both the argument
Tees and combine their outputs using the Fractional instance of the
output type.
(Monadm, Numb) => Num (Teemab)Defined in streamly-core-0.3.1 · Streamly.Internal.Data.Fold.Tee
Binary Num operations distribute the input to both the argument Tees
and combine their outputs using the Num instance of the output type.
(Semigroupb, Monadm) => Semigroup (Teemab)Defined in streamly-core-0.3.1 · Streamly.Internal.Data.Fold.Tee
<> distributes the input to both the argument Tees and combines their
outputs using the Semigroup instance of the output type.
(Monoidb, Monadm) => Monoid (Teemab)Defined in streamly-core-0.3.1 · Streamly.Internal.Data.Fold.Tee
<> distributes the input to both the argument Tees and combines their
outputs using the Monoid instance of the output type.
Running A Fold
4 declarations
Streamly.Data.Strem.fold and drive are the basic fold runners.
Folds can also be used a incremental builders. 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.
Append a stream to a fold to build the fold accumulator incrementally. We
can repeatedly call addStream on the same fold to continue building the
fold and finally use drive to finish the fold and extract the result. Also
see the Streamly.Data.Fold.addOne operation which is a singleton version
of addStream.
duplicate provides the ability to run a fold in parts. The duplicated
fold consumes the input and returns the same fold as output instead of
returning the final result, the returned fold can be run later to consume
more input.
duplicate essentially appends a stream to the fold without finishing the
fold. Compare with snoc which appends a singleton value to the fold.
Make a fold using a right fold style step function and a terminal value.
It performs a strict right fold via a left fold using function composition.
Note that a strict right fold can only be useful for constructing strict
structures in memory. For reductions this will be very inefficient.
Definitions:
Example2 expressions
>>> foldr' f z = fmap (flip appEndo z) $ Fold.foldMap (Endo . f)>>> foldr' f z = fmap ($ z) $ Fold.foldl' (\g x -> g . f x) id
Make a terminating fold with an effectful step function and initial state,
and a state extraction function.
Example1 expression
>>> foldtM' = Fold.Fold
We can just use Fold but it is provided for completeness.
Pre-release
Folds
0 declarations
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.
The memory used is proportional to the number of distinct elements in the
stream, to guard against using too much memory use it as a scan and
terminate if the count reaches more than a threshold.
Determine the sum of all elements of a stream of numbers. Returns additive
identity (0) when the stream is empty. Note that this is not numerically
stable for floating point numbers.
Example1 expression
>>> sum = Fold.fromScanl (Scanl.cumulativeScan Scanl.incrSum)
Same as following but numerically stable:
Example2 expressions
>>> sum = Fold.foldl' (+) 0>>> sum = fmap Data.Monoid.getSum $ Fold.foldMap Data.Monoid.Sum
Determine the product of all elements of a stream of numbers. Returns
multiplicative identity (1) when the stream is empty. The fold terminates
when it encounters (0) in its input.
Same as the following but terminates on multiplication by 0:
Return the latest unique element using the supplied comparison function.
Returns Nothing if the current element is same as the last element
otherwise returns Just.
Example, strip duplicate path separators:
Example2 expressions
>>> input = Stream.fromList "//a//b">>> f x y = x == '/' && y == '/'
Terminates with Nothing as soon as it finds an element different than
the previous one, returns the element if the entire input consists of the
same element.
Returns True if any element is True, False otherwise
Definition:
Example1 expression
>>> or = Fold.any (== True)
Transformations
0 declarations
Transformations 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, transformations have 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:
Example1 expression
>>> Stream.fold (fmap show Fold.sum) (Stream.enumerateFromTo 1 100)"5050"
Continue taking the input until the input sequence matches the supplied
sequence, taking the supplied sequence as well. If the pattern is empty this
acts as an identity fold.
Example3 expressions
>>> s = Stream.fromList "Gauss---Euler---Noether">>> f = Fold.takeEndBySeq (Array.fromList "---") Fold.toList>>> Stream.fold f s"Gauss---"
Example1 expression
>>> Stream.fold Fold.toList $ Stream.foldMany f s["Gauss---","Euler---","Noether"]
Uses Rabin-Karp algorithm for substring search.
See also: Streamly.Data.Stream.splitOnSeq and
Streamly.Data.Stream.splitEndBySeq.
Split the input stream based on a key field and fold each split using the
given fold. Useful for map/reduce, bucketizing the input in different bins
or for generating histograms.
Once the classifier fold terminates for a particular key any further inputs
in that bucket are ignored.
Space used is proportional to the number of keys seen till now and
monotonically increases because it stores whether a key has been seen or
not.
See demuxToMap for a more powerful version where you can use a different
fold for each key. A simpler version of toMap retaining only the last
value for a key can be written as:
Example1 expression
>>> toMap = Fold.foldl' (\kv (k, v) -> Map.insert k v kv) Map.empty
Sequential fold application. Apply two folds sequentially to an input
stream. The input is provided to the first fold, when it is done - the
remaining input is provided to the second fold. When the second fold is done
or if the input stream is over, the outputs of the two folds are combined
using the supplied function.
Example:
Example4 expressions
>>> header = Fold.take 8 Fold.toList>>> line = Fold.takeEndBy (== '\n') Fold.toList>>> f = Fold.splitWith (,) header line>>> Stream.fold f $ Stream.fromList "header: hello\n"("header: ","hello\n")
Note: This is dual to appending streams using Data.Stream.append.
Note: this implementation allows for stream fusion but has quadratic time
complexity, because each composition adds a new branch that each subsequent
fold's input element has to traverse, therefore, it cannot scale to a large
number of compositions. After around 100 compositions the performance starts
dipping rapidly compared to a CPS style implementation.
For larger number of compositions you can convert the fold to a parser and
use ParserK.
Time: O(n^2) where n is the number of compositions.
Collect zero or more applications of a fold. many first second applies
the first fold repeatedly on the input stream and accumulates it's results
using the second fold.
Example3 expressions
>>> two = Fold.take 2 Fold.toList>>> twos = Fold.many two Fold.toList>>> Stream.fold twos $ Stream.fromList [1..10][[1,2],[3,4],[5,6],[7,8],[9,10]]
Stops when second fold stops.
See also: Data.Stream.concatMap, Data.Stream.foldMany
Compose two folds such that the combined fold accepts a stream of Either
and routes the Left values to the first fold and Right values to the
second fold.
demuxerToMap getKey getFold: In a key value stream, fold values
corresponding to each key using a key specific fold. getFold is invoked to
generate a key specific fold when a key is encountered for the first time in
the stream.
If a fold terminates, another instance of the fold is started upon receiving
an input with that key, getFold is invoked again whenever the key is
encountered again.
This combinator can be used to scan a stream and collect the results from
the scan output.
Since the fold generator function is monadic, folds for new keys can be
added dynamically or folds for old keys can be deleted or modified. For
example, we can maintain a Map of keys to folds in an IORef and lookup the
fold from that corresponding to a key. This Map can be changed dynamically.
Note that this fold never terminates. Inputs that do not correspond to a
fold in the map are dropped.
Map a Fold returning function on the result of a Fold and run the
returned fold. This is akin to an n-ary version of splitWith where the
next fold for splitting the input is decided dynamically using the previous
result. This operation can be used to express data dependencies
between fold operations.
Let's say the first element in the stream is a count of the following
elements that we have to add, then:
Example4 expressions
>>> import Data.Maybe (fromJust)>>> count = fmap fromJust Fold.one>>> total n = Fold.take n Fold.sum>>> Stream.fold (Fold.concatMap total count) $ Stream.fromList [10,9..1]45
This does not fuse completely, see refold for a fusible alternative.
Time: O(n^2) where n is the number of compositions.
Folds the values for each key using the supplied fold. When scanning, as
soon as the fold is complete, its result is available in the second
component of the tuple. The first component of the tuple is a snapshot of
the in-progress folds.
Once the fold for a key is done, any future values of the key are ignored.
Same as classify except that it uses mutable IORef cells in the
Map providing better performance. Be aware that if this is used as a scan,
the values in the intermediate Maps would be mutable.
demux getKey getFold: In a key value stream, fold values corresponding
to each key using a key specific fold. getFold is invoked to generate a
key specific fold when a key is encountered for the first time in the
stream.
The first component of the output tuple is a key-value Map of in-progress
folds. The fold returns the fold result as the second component of the
output tuple whenever a fold terminates.
If a fold terminates, another instance of the fold is started upon receiving
an input with that key, getFold is invoked again whenever the key is
encountered again.
This can be used to scan a stream and collect the results from the scan
output.
Since the fold generator function is monadic we can add folds dynamically.
For example, we can maintain a Map of keys to folds in an IORef and lookup
the fold from that corresponding to a key. This Map can be changed
dynamically, folds for new keys can be added or folds for old keys can be
deleted or modified.