@@ -8,15 +8,20 @@
88-- Stability : released
99-- Portability : GHC
1010--
11−-- Fast, composable stream producers with ability to terminate, supporting
12−-- stream fusion.
11+-- Streams represented as state machines, that fuse together when composed
12+-- statically, eliminating function calls or intermediate constructor
13+-- allocations - generating tight, efficient loops. Suitable for high
14+-- performance looping operations.
15+--
16+-- If you need to call these operations recursively in a loop (i.e. composed
17+-- dynamically) then it is recommended to use the continuation passing style
18+-- (CPS) stream operations from the "Streamly.Data.StreamK" module. 'Stream'
19+-- and 'StreamK' types are interconvertible. See more details in the
20+-- documentation below regarding 'Stream' vs 'StreamK'.
1321--
1422-- Please refer to "Streamly.Internal.Data.Stream" for more functions that have
1523-- not yet been released.
1624--
17−-- For continuation passing style (CPS) stream type, please refer to
18−-- the "Streamly.Data.StreamK" module.
19−--
2025-- Checkout the <https://github.com/composewell/streamly-examples>
2126-- repository for many more real world examples of stream programming.
2227
@@ -49,11 +54,12 @@ module Streamly.Data.Stream
4954 -- >>> fromFoldableM = Stream.sequence . fromFoldable
5055
5156 -- ** Primitives
52− -- | A fused 'Stream' is never constructed using 'cons', we always convert
53− -- other type of containers like list into streams, or generate it using
54− -- custom functions provided in this module. The 'cons' primitive has a
55− -- rare use in fusing a small number of elements. On the other hand, it is
56− -- common to construct 'StreamK' stream using the 'StreamK.cons' primitive.
57+ -- | A fused 'Stream' is never constructed using these primitives, they are
58+ -- typically generated by converting containers like list into streams, or
59+ -- generated using custom functions provided in this module. The 'cons'
60+ -- primitive in this module has a rare use in fusing a small number of
61+ -- elements. On the other hand, it is common to construct 'StreamK' stream
62+ -- using the StreamK.'StreamK.cons' primitive.
5763 , nil
5864 , nilM
5965 , cons
@@ -358,32 +364,31 @@ module Streamly.Data.Stream
358364 -- , elemIndices
359365
360366 -- * Combining Two Streams
367+ -- | Note that these operations are suitable for statically fusing a few
368+ -- streams, they have a quadratic O(n^2) time complexity wrt to the number
369+ -- of streams. If you want to compose many streams dynamically using binary
370+ -- combining operations see the corresponding operations in
371+ -- "Streamly.Data.StreamK".
372+ --
373+ -- When fusing more than two streams it is more efficient if the binary
374+ -- operations are composed as a balanced tree rather than a right
375+ -- associative or left associative one e.g.:
376+ --
377+ -- >>> s1 = Stream.fromList [1,2] `Stream.append` Stream.fromList [3,4]
378+ -- >>> s2 = Stream.fromList [4,5] `Stream.append` Stream.fromList [6,7]
379+ -- >>> s = s1 `Stream.append` s2
380+
361381 -- ** Appending
362382 , append
363383
364384 -- ** Interleaving
365− -- | When interleaving more than two streams you may want to interleave
366− -- them pairwise creating a balanced binary merge tree.
367385 , interleave
368386
369387 -- ** Merging
370− -- | When merging more than two streams you may want to merging them
371− -- pairwise creating a balanced binary merge tree.
372− --
373− -- Merging of @n@ streams can be performed by combining the streams pair
374− -- wise using 'mergeMapWith' to give O(n * log n) time complexity. If used
375− -- with 'concatMapWith' it will have O(n^2) performance.
376−
377388 , mergeBy
378389 , mergeByM
379390
380391 -- ** Zipping
381− -- | When zipping more than two streams you may want to zip them
382− -- pairwise creating a balanced binary tree.
383− --
384− -- Zipping of @n@ streams can be performed by combining the streams pair
385− -- wise using 'mergeMapWith' with O(n * log n) time complexity. If used
386− -- with 'concatMapWith' it will have O(n^2) performance.
387392 , zipWith
388393 , zipWithM
389394 -- , ZipStream (..)
@@ -573,11 +578,11 @@ import qualified Streamly.Internal.Data.Stream.Chunked as ArrayStream
573578--
574579-- == Stream Fusion
575580--
576−-- The fused 'Stream' type employs stream fusion for C-like performance when
577−-- looping over data. It represents a stream source or transformation by
578−-- defining a state machine with explicit state, and a step function working on
579−-- the state. A typical stream operation consumes elements from the previous
580−-- state machine in the pipeline, transforms them and yields new values for the
581+-- The fused 'Stream' type in this module employs stream fusion for C-like
582+-- performance when looping over data. It represents the stream as a state
583+-- machine using an explicit state, and a step function working on the state. A
584+-- typical stream operation consumes elements from the previous state machine
585+-- in a stream pipeline, transforms the elements and yields new values for the
581586-- next stage to consume. The stream operations are modular and represent a
582587-- single task, they have no knowledge of previous or next operation on the
583588-- elements.
@@ -586,7 +591,7 @@ import qualified Streamly.Internal.Data.Stream.Chunked as ArrayStream
586591-- transformation operations and a stream consumer. All these operations taken
587592-- together form a closed loop processing the stream elements. Elements are
588593-- transferred between stages using a boxed data constructor. However, all the
589−-- stages of the pipeline are fused together by GHC, eliminating the
594+-- stages of the pipeline are fused together by GHC, eliminating the boxing of
590595-- intermediate constructors, and thus forming a tight C like loop without any
591596-- boxed data being used in the loop.
592597--
@@ -601,43 +606,45 @@ import qualified Streamly.Internal.Data.Stream.Chunked as ArrayStream
601606--
602607-- == Stream vs StreamK
603608--
604−-- The fused stream model avoids constructor allocations or function call
605−-- overheads. However, the stream is represented as a state machine and to
606−-- generate elements it has to navigate the decision tree of the state machine.
607−-- Moreover, the state machine is cranked for each element in the stream. This
608−-- performs extremely well when the number of states are limited. The state
609−-- machine starts getting expensive as the number of states increase. For
610−-- example, generating a million element stream from a list requires a single
611−-- state and is very efficient. However, using fused 'cons' to generate a
612−-- million element stream would be a disaster.
609+-- The fused stream model avoids constructor allocations and function call
610+-- overheads. However, the stream is represented as a state machine, and to
611+-- generate stream elements it has to navigate the decision tree of the state
612+-- machine. Moreover, the state machine is cranked for each element in the
613+-- stream. This performs extremely well when the number of states are limited.
614+-- The state machine starts getting expensive as the number of states increase.
615+-- For example, generating a stream from a list requires a single state and is
616+-- very efficient, even if it has millions of elements. However, using 'cons'
617+-- to construct a million element stream would be a disaster.
613618--
614619-- A typical worst case scenario for fused stream model is a large number of
615620-- `cons` or `append` operations. A few static `cons` or `append` operations
616−-- are very fast and much faster than a CPS style stream. However, if we
617−-- construct a large stream using `cons` it introduces as many states in the
618−-- state machine as the number of elements. If we compose the `cons` as a
619−-- binary tree it will take @n * log n@ time to navigate the tree, and @n * n@
620−-- if it is a right associative composition.
621−--
622−-- For quadratic cases of fused stream, after a certain threshold the CPS
623−-- stream would perform much better and exhibit linear performance behavior.
621+-- are very fast and much faster than a CPS style stream because CPS involves a
622+-- function call for each element whereas fused stream involves a few
623+-- conditional branches in the state machine. However, constructing a large
624+-- stream using `cons` introduces as many states in the state machine as the
625+-- number of elements. If we compose `cons` as a balanced binary tree it will
626+-- take @n * log n@ time to navigate the tree, and @n * n@ if it is a right
627+-- associative composition.
628+--
624629-- Operations like 'cons' or 'append'; are typically recursively called to
625630-- construct a lazy infinite stream. For such use cases the CPS style 'StreamK'
626−-- type is provided. CPS streams do not have a state machine that needs to be
631+-- should be used. CPS streams do not have a state machine that needs to be
627632-- cranked for each element, past state has no effect on the future element
628−-- processing. However, it incurs a function call overhead for each operation
629−-- for each element, which could be very large overhead compared to fused state
630−-- machines even if it has many states and cranks it for each element. But in
631−-- some cases scales tip in favor of the CPS stream. In those cases even though
632−-- CPS has a large constant overhead, it has a linear performance rather than
633−-- quadratic.
634−--
635−-- As a general guideline, if you have to use 'cons' or 'append' or operations
636−-- of similar nature, at a large scale, then 'StreamK' should be used. When you
637−-- need to compose the stream dynamically or recursively, then 'StreamK' should
638−-- be used. Typically you would use a dynamically generated 'StreamK' with
639−-- chunks of data which can then be processed by statically fused stream
640−-- pipeline operations.
633+-- processing. However, CPS incurs a function call overhead for each element
634+-- processed, the overhead could be large compared to a fused state machine
635+-- even if it has many states. However, because of its linear performance
636+-- characterstics, after a certain threshold of stream compositions the CPS
637+-- stream would perform much better than the quadratic fused stream operations.
638+--
639+-- As a general guideline, you need to use 'StreamK' when you have to use
640+-- 'cons', 'append' or other operations having quadratic complexity at a large
641+-- scale. Typically, in such cases you need to compose the stream recursively,
642+-- by calling an operation in a loop. The decision to compose the stream is
643+-- taken at run time rather than statically at compile time.
644+--
645+-- Typically you would compose a 'StreamK' of chunks of data so that the
646+-- StreamK overhead is not high, and then process the chunks using 'Stream' by
647+-- using statically fused stream pipeline operations on the chunks.
641648--
642649-- 'Stream' and 'StreamK' types can be interconverted. See
643650-- "Streamly.Data.StreamK" module for conversion operations.