on master help sign in
Update StreamK documentation
e694d88Harendra Kumar
core/src/DocTestDataStreamK.hs+2
@@ -4,6 +4,8 @@
4>>> import Data.Function (fix, (&))
4>>> import Data.Function (fix, (&))
5>>> import Data.Semigroup (cycle1)
5>>> import Data.Semigroup (cycle1)
6
6
7+>>> effect n = print n >> return n
8+
7>>> import Streamly.Data.StreamK (StreamK)
9>>> import Streamly.Data.StreamK (StreamK)
8>>> import qualified Streamly.Data.Fold as Fold
10>>> import qualified Streamly.Data.Fold as Fold
9>>> import qualified Streamly.Data.Parser as Parser
11>>> import qualified Streamly.Data.Parser as Parser
core/src/Streamly/Data/Stream.hs+73−24
@@ -14,6 +14,9 @@
14-- Please refer to "Streamly.Internal.Data.Stream" for more functions that have
14-- Please refer to "Streamly.Internal.Data.Stream" for more functions that have
15-- not yet been released.
15-- not yet been released.
16--
16--
17+-- For continuation passing style (CPS) stream type, please refer to
18+-- the "Streamly.Data.StreamK" module.
19+--
17-- Checkout the <https://github.com/composewell/streamly-examples>
20-- Checkout the <https://github.com/composewell/streamly-examples>
18-- repository for many more real world examples of stream programming.
21-- repository for many more real world examples of stream programming.
19
22
@@ -501,30 +504,76 @@ import Prelude
501-- "Data.List" like functions and many more powerful combinators to perform
504-- "Data.List" like functions and many more powerful combinators to perform
502-- common programming tasks.
505-- common programming tasks.
503--
506--
504−-- == Performance Notes
507+-- == Stream Fusion
505−--
508+--
506−-- The 'Stream' type represents a stream by composing state as data which
509+-- The fused 'Stream' type employs stream fusion for C-like performance when
507−-- enables stream fusion. Stream fusion generates a tight loop without any
510+-- looping over data. It represents a stream source or transformation by
508−-- constructor allocations between the stages, providing C like performance for
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+--
509−-- the loop. Stream fusion works when multiple functions are combined in a
526+-- Stream fusion works effectively when:
510−-- pipeline statically. Therefore, the operations in this module must be
527+--
511−-- inlined and must not be used recursively to allow for stream fusion.
528+-- * the stream pipeline is composed statically (known at compile time)
512−--
529+-- * all the operations forming the loop are inlined
513−-- Using the 'Stream' type binary stream construction operations like 'cons',
530+-- * the loop is not recursively defined, recursion breaks inlining
514−-- 'append' etc. degrade quadratically (O(n^2)) when combined many times. If
531+--
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'
532+-- If these conditions cannot be met, the CPS style stream type 'StreamK' may
517−-- instead. Also, if you need to use these operations in a recursive loop you
533+-- turn out to be a better choice than the fused stream type 'Stream'.
518−-- should use 'StreamK' instead.
534+--
519−--
535+-- == Stream vs StreamK
520−-- The 'StreamK' type represents a stream by composing function calls,
536+--
521−-- therefore, a function call overhead is incurred at each composition. It is
537+-- The fused stream model avoids constructor allocations or function call
522−-- quite fast in general but may be a few times slower than a fused stream.
538+-- overheads. However, the stream is represented as a state machine and to
523−-- However, it allows for scalable dynamic composition and control flow
539+-- generate elements it has to navigate the decision tree of the state machine.
524−-- manipulation. Using the 'StreamK' type binary operations like 'cons' and
540+-- Moreover, the state machine is cranked for each element in the stream. This
525−-- 'append' provide linear (O(n)) performance.
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.
526−--
574+--
527−-- 'Stream' and 'StreamK' types can be interconverted.
575+-- 'Stream' and 'StreamK' types can be interconverted. See
576+-- "Streamly.Data.StreamK" module for conversion operations.
528--
577--
529-- == Useful Idioms
578-- == Useful Idioms
530--
579--
core/src/Streamly/Data/StreamK.hs+50−21
@@ -8,29 +8,15 @@
8-- Stability : released
8-- Stability : released
9-- Portability : GHC
9-- Portability : GHC
10--
10--
11−-- Streams using Continuation Passing Style (CPS). See notes in "Data.Stream"
11+-- Streams using Continuation Passing Style (CPS). See the @Stream vs StreamK@
12−-- module to know when to use this module.
12+-- section in the "Streamly.Data.Stream" module to know when to use this
13+-- module.
13--
14--
14−-- 'StreamK' operations have more overhead than 'Stream' operations, however,
15+-- Please refer to "Streamly.Internal.Data.Stream.StreamK" for more functions
15−-- 'Stream' operations fuse only when the pipeline is statically composed, is
16+-- that have not yet been released.
16−-- not too large, and does not involve recursion. For such cases, a typical
17−-- example is creating a large or infinite stream by appending, 'StreamK'
18−-- should be used.
19−--
20−-- You can use operations from "Streamly.Data.Stream" for StreamK as well by
21−-- converting StreamK to Stream, and vice-versa. Most of the time converting to
22−-- 'Stream' is better than using native StreamK operations, however in certain
23−-- cases using native StreamK operations may work better. Using 'Stream'
24−-- operations are especially when multiple contiguous operations can fuse
25−-- together.
26−--
27−-- There are no native exception handling operations in the StreamK module,
28−-- please use 'toStream' to convert to 'Stream' type and use exception
29−-- handling operations from "Streamly.Data.Stream".
30−--
31−-- See "Streamly.Internal.Data.Stream.StreamK" for more native StreamK
32−-- operations.
33
17
18+-- Notes:
19+--
34-- primitive/open loop operations that can be used recursively e.g. uncons,
20-- primitive/open loop operations that can be used recursively e.g. uncons,
35-- foldBreak, parseBreak should not be converted from StreamD for use in
21-- foldBreak, parseBreak should not be converted from StreamD for use in
36-- StreamK, instead native StreamK impl should be used.
22-- StreamK, instead native StreamK impl should be used.
@@ -44,6 +30,16 @@
44--
30--
45module Streamly.Data.StreamK
31module Streamly.Data.StreamK
46 (
32 (
33+ -- * Setup
34+ -- | To execute the code examples provided in this module in ghci, please
35+ -- run the following commands first.
36+ --
37+ -- $setup
38+
39+ -- * Overview
40+ -- $overview
41+
42+ -- * Type
47 StreamK
43 StreamK
48
44
49 -- * Construction
45 -- * Construction
@@ -122,3 +118,36 @@ import Streamly.Internal.Data.Stream.StreamK
122import Prelude hiding (reverse, zipWith, mapM, dropWhile, take)
118import Prelude hiding (reverse, zipWith, mapM, dropWhile, take)
123
119
124#include "DocTestDataStreamK.hs"
120#include "DocTestDataStreamK.hs"
121+
122+-- $overview
123+--
124+-- Continuation passing style (CPS) stream implementation. The 'K' in 'StreamK'
125+-- stands for Kontinuation.
126+--
127+-- StreamK can be constructed like lists, except that they use 'nil' instead of
128+-- '[]' and 'cons' instead of ':'.
129+--
130+-- `cons` adds a pure value at the head of the stream:
131+--
132+-- >>> import Streamly.Data.StreamK (StreamK, cons, consM, nil)
133+-- >>> stream = 1 `cons` 2 `cons` nil :: StreamK IO Int
134+--
135+-- You can use operations from "Streamly.Data.Stream" for StreamK as well by
136+-- converting StreamK to Stream ('toStream'), and vice-versa ('fromStream').
137+--
138+-- >>> Stream.fold Fold.toList $ StreamK.toStream stream -- IO [Int]
139+-- [1,2]
140+--
141+-- `consM` adds an effect at the head of the stream:
142+--
143+-- >>> stream = effect 1 `consM` effect 2 `consM` nil
144+-- >>> Stream.fold Fold.toList $ StreamK.toStream stream
145+-- 1
146+-- 2
147+-- [1,2]
148+--
149+-- == Exception Handling
150+--
151+-- There are no native exception handling operations in the StreamK module,
152+-- please convert to 'Stream' type and use exception handling operations from
153+-- "Streamly.Data.Stream".
core/src/Streamly/Internal/Data/Stream/StreamK.hs+6−41
@@ -8,47 +8,14 @@
8-- Stability : experimental
8-- Stability : experimental
9-- Portability : GHC
9-- Portability : GHC
10--
10--
11−-- To run examples in this module:
12−--
13−-- >>> import qualified Streamly.Data.Fold as Fold
14−-- >>> import qualified Streamly.Data.Stream as Stream
15−-- >>> import qualified Streamly.Data.StreamK as StreamK
16−--
17−-- We will add some more imports in the examples as needed.
18−--
19−-- For effectful streams we will use the following IO action:
20−--
21−-- >>> effect n = print n >> return n
22−--
23−-- = Overview
24−--
25−-- Continuation passing style (CPS) stream implementation. The 'K' in 'StreamK'
26−-- stands for Kontinuation.
27−--
28−-- StreamK can be constructed like lists, except that they use 'nil' instead of
29−-- '[]' and 'cons' instead of ':'.
30−--
31−-- `cons` adds a pure value at the head of the stream:
32−--
33−-- >>> import Streamly.Data.StreamK (StreamK, cons, consM, nil)
34−-- >>> stream = 1 `cons` 2 `cons` nil :: StreamK IO Int
35−--
36−-- Convert 'StreamK' to 'Stream' to use functions from the
37−-- "Streamly.Data.Stream" module:
38−--
39−-- >>> Stream.fold Fold.toList $ StreamK.toStream stream -- IO [Int]
40−-- [1,2]
41−--
42−-- `consM` adds an effect at the head of the stream:
43−--
44−-- >>> stream = effect 1 `consM` effect 2 `consM` nil
45−-- >>> Stream.fold Fold.toList $ StreamK.toStream stream
46−-- 1
47−-- 2
48−-- [1,2]
49−--
50module Streamly.Internal.Data.Stream.StreamK
11module Streamly.Internal.Data.Stream.StreamK
51 (
12 (
13+ -- * Setup
14+ -- | To execute the code examples provided in this module in ghci, please
15+ -- run the following commands first.
16+ --
17+ -- $setup
18+
52 -- * The stream type
19 -- * The stream type
53 Stream
20 Stream
54 , StreamK(..)
21 , StreamK(..)
@@ -916,14 +883,12 @@ mapMaybe f = go
916
883
917-- | Zip two streams serially using a pure zipping function.
884-- | Zip two streams serially using a pure zipping function.
918--
885--
919−-- @since 0.1.0
920{-# INLINE zipWith #-}
886{-# INLINE zipWith #-}
921zipWith :: Monad m => (a -> b -> c) -> StreamK m a -> StreamK m b -> StreamK m c
887zipWith :: Monad m => (a -> b -> c) -> StreamK m a -> StreamK m b -> StreamK m c
922zipWith f = zipWithM (\a b -> return (f a b))
888zipWith f = zipWithM (\a b -> return (f a b))
923
889
924-- | Zip two streams serially using a monadic zipping function.
890-- | Zip two streams serially using a monadic zipping function.
925--
891--
926−-- @since 0.1.0
927{-# INLINE zipWithM #-}
892{-# INLINE zipWithM #-}
928zipWithM :: Monad m =>
893zipWithM :: Monad m =>
929 (a -> b -> m c) -> StreamK m a -> StreamK m b -> StreamK m c
894 (a -> b -> m c) -> StreamK m a -> StreamK m b -> StreamK m c
core/src/Streamly/Internal/Data/Stream/StreamK/Type.hs+1−5
@@ -280,7 +280,6 @@ infixr 5 .:
280-- [1,2,3]
280-- [1,2,3]
281-- @
281-- @
282--
282--
283−-- @since 0.1.1
284{-# INLINE (.:) #-}
283{-# INLINE (.:) #-}
285(.:) :: a -> StreamK m a -> StreamK m a
284(.:) :: a -> StreamK m a -> StreamK m a
286(.:) = cons
285(.:) = cons
@@ -1848,13 +1847,10 @@ mfix f = mkStream $ \st yld sng stp ->
1848-------------------------------------------------------------------------------
1847-------------------------------------------------------------------------------
1849
1848
1850-- |
1849-- |
1851−-- @
1852−-- fromFoldable = 'Prelude.foldr' 'cons' 'nil'
1850+-- >>> fromFoldable = Prelude.foldr StreamK.cons StreamK.nil
1853−-- @
1854--
1851--
1855-- Construct a stream from a 'Foldable' containing pure values:
1852-- Construct a stream from a 'Foldable' containing pure values:
1856--
1853--
1857−-- @since 0.2.0
1858{-# INLINE fromFoldable #-}
1854{-# INLINE fromFoldable #-}
1859fromFoldable :: Foldable f => f a -> StreamK m a
1855fromFoldable :: Foldable f => f a -> StreamK m a
1860fromFoldable = Prelude.foldr cons nil
1856fromFoldable = Prelude.foldr cons nil