@@ -14,6 +14,9 @@
1414-- Please refer to "Streamly.Internal.Data.Stream" for more functions that have
1515-- not yet been released.
1616--
17+-- For continuation passing style (CPS) stream type, please refer to
18+-- the "Streamly.Data.StreamK" module.
19+--
1720-- Checkout the <https://github.com/composewell/streamly-examples>
1821-- repository for many more real world examples of stream programming.
1922
@@ -501,30 +504,76 @@ import Prelude
501504-- "Data.List" like functions and many more powerful combinators to perform
502505-- common programming tasks.
503506--
504−-- == Performance Notes
505−--
506−-- The 'Stream' type represents a stream by composing state as data which
507−-- enables stream fusion. Stream fusion generates a tight loop without any
508−-- constructor allocations between the stages, providing C like performance for
509−-- the loop. Stream fusion works when multiple functions are combined in a
510−-- pipeline statically. Therefore, the operations in this module must be
511−-- inlined and must not be used recursively to allow for stream fusion.
512−--
513−-- Using the 'Stream' type binary stream construction operations like 'cons',
514−-- 'append' etc. degrade quadratically (O(n^2)) when combined many times. If
515−-- you need to combine these operations, say more than 50 times in a single
516−-- loop, then you should use the continuation style stream type 'StreamK'
517−-- instead. Also, if you need to use these operations in a recursive loop you
518−-- should use 'StreamK' instead.
519−--
520−-- The 'StreamK' type represents a stream by composing function calls,
521−-- therefore, a function call overhead is incurred at each composition. It is
522−-- quite fast in general but may be a few times slower than a fused stream.
523−-- However, it allows for scalable dynamic composition and control flow
524−-- manipulation. Using the 'StreamK' type binary operations like 'cons' and
525−-- 'append' provide linear (O(n)) performance.
526−--
527−-- 'Stream' and 'StreamK' types can be interconverted.
507+-- == Stream Fusion
508+--
509+-- The fused 'Stream' type employs stream fusion for C-like performance when
510+-- looping over data. It represents a stream source or transformation by
511+-- defining a state machine with explicit state, and a step function working on
512+-- the state. A typical stream operation consumes elements from the previous
513+-- state machine in the pipeline, transforms them and yields new values for the
514+-- next stage to consume. The stream operations are modular and represent a
515+-- single task, they have no knowledge of previous or next operation on the
516+-- elements.
517+--
518+-- A typical stream pipeline consists of a stream producer, several stream
519+-- transformation operations and a stream consumer. All these operations taken
520+-- together form a closed loop processing the stream elements. Elements are
521+-- transferred between stages using a boxed data constructor. However, all the
522+-- stages of the pipeline are fused together by GHC, eliminating the
523+-- intermediate constructors, and thus forming a tight C like loop without any
524+-- boxed data being used in the loop.
525+--
526+-- Stream fusion works effectively when:
527+--
528+-- * the stream pipeline is composed statically (known at compile time)
529+-- * all the operations forming the loop are inlined
530+-- * the loop is not recursively defined, recursion breaks inlining
531+--
532+-- If these conditions cannot be met, the CPS style stream type 'StreamK' may
533+-- turn out to be a better choice than the fused stream type 'Stream'.
534+--
535+-- == Stream vs StreamK
536+--
537+-- The fused stream model avoids constructor allocations or function call
538+-- overheads. However, the stream is represented as a state machine and to
539+-- generate elements it has to navigate the decision tree of the state machine.
540+-- Moreover, the state machine is cranked for each element in the stream. This
541+-- performs extremely well when the number of states are limited. The state
542+-- machine starts getting expensive as the number of states increase. For
543+-- example, generating a million element stream from a list requires a single
544+-- state and is very efficient. However, using fused 'cons' to generate a
545+-- million element stream would be a disaster.
546+--
547+-- A typical worst case scenario for fused stream model is a large number of
548+-- `cons` or `append` operations. A few static `cons` or `append` operations
549+-- are very fast and much faster than a CPS style stream. However, if we
550+-- construct a large stream using `cons` it introduces as many states in the
551+-- state machine as the number of elements. If we compose the `cons` as a
552+-- binary tree it will take @n * log n@ time to navigate the tree, and @n * n@
553+-- if it is a right associative composition.
554+--
555+-- For quadratic cases of fused stream, after a certain threshold the CPS
556+-- stream would perform much better and exhibit linear performance behavior.
557+-- Operations like 'cons' or 'append'; are typically recursively called to
558+-- construct a lazy infinite stream. For such use cases the CPS style 'StreamK'
559+-- type is provided. CPS streams do not have a state machine that needs to be
560+-- cranked for each element, past state has no effect on the future element
561+-- processing. However, it incurs a function call overhead for each operation
562+-- for each element, which could be very large overhead compared to fused state
563+-- machines even if it has many states and cranks it for each element. But in
564+-- some cases scales tip in favor of the CPS stream. In those cases even though
565+-- CPS has a large constant overhead, it has a linear performance rather than
566+-- quadratic.
567+--
568+-- As a general guideline, if you have to use 'cons' or 'append' or operations
569+-- of similar nature, at a large scale, then 'StreamK' should be used. When you
570+-- need to compose the stream dynamically or recursively, then 'StreamK' should
571+-- be used. Typically you would use a dynamically generated 'StreamK' with
572+-- chunks of data which can then be processed by statically fused stream
573+-- pipeline operations.
574+--
575+-- 'Stream' and 'StreamK' types can be interconverted. See
576+-- "Streamly.Data.StreamK" module for conversion operations.
528577--
529578-- == Useful Idioms
530579--