on master help sign in
Make some doc updates to Stream and StreamK modules
commitb7a2e954e9b39a50aa27d131eb44b042821dabc0 authorHarendra Kumar <harendra@composewell.com> dateThu 10 Aug 2023, 10:00 (3 years ago) parent022a05e
7 files changed+187−146
core/src/Streamly/Data/Stream.hs+69−62 core/src/Streamly/Data/StreamK.hs+35−18 core/src/Streamly/Internal/Data/Stream/StreamD/Generate.hs+22−10 core/src/Streamly/Internal/Data/Stream/StreamD/Nesting.hs+24−21 core/src/Streamly/Internal/Data/Stream/StreamD/Type.hs+5−1 core/src/Streamly/Internal/Data/Stream/StreamK/Type.hs+7−24 core/src/Streamly/Internal/Data/StreamK.hs+25−10
core/src/Streamly/Data/Stream.hs+69−62
@@ -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.
core/src/Streamly/Data/StreamK.hs+35−18
@@ -8,18 +8,30 @@
88-- Stability : released
99-- Portability : GHC
1010--
11−-- Streams using Continuation Passing Style (CPS). See the @Stream vs StreamK@
12−-- section in the "Streamly.Data.Stream" module to know when to use this
13−-- module.
14−--
15−-- Please refer to "Streamly.Internal.Data.StreamK" for more functions
16−-- that have not yet been released.
17−--
18−-- Some useful idioms:
19−--
20−-- >>> concatFoldableWith f = Prelude.foldr f StreamK.nil
21−-- >>> concatMapFoldableWith f g = Prelude.foldr (f . g) StreamK.nil
22−-- >>> concatForFoldableWith f xs g = Prelude.foldr (f . g) StreamK.nil xs
11+-- Streams represented as chains of functions calls using Continuation Passing
12+-- Style (CPS), suitable for dynamically composing potentially large number of
13+-- streams.
14+--
15+-- Unlike the statically fused operations in "Streamly.Data.Stream", StreamK
16+-- operations are less efficient, involving a function call overhead for each
17+-- element, but they exhibit linear O(n) time complexity wrt to the number of
18+-- stream compositions. Therefore, they are suitable for dynamically composing
19+-- streams e.g. appending potentially infinite streams in recursive loops.
20+-- While fused streams can be used to efficiently process elements as small as
21+-- a single byte, CPS streams are typically used on bigger chunks of data to
22+-- avoid the larger overhead per element. For more details See the @Stream vs
23+-- StreamK@ section in the "Streamly.Data.Stream" module.
24+--
25+-- In addition to the combinators in this module, you can use operations from
26+-- "Streamly.Data.Stream" for StreamK as well by converting StreamK to Stream
27+-- ('toStream'), and vice-versa ('fromStream'). Please refer to
28+-- "Streamly.Internal.Data.StreamK" for more functions that have not yet been
29+-- released.
30+--
31+-- For documentation see the corresponding combinators in
32+-- "Streamly.Data.Stream". Documentation has been omitted in this module unless
33+-- there is a difference worth mentioning or if the combinator does not exist
34+-- in "Streamly.Data.Stream".
2335
2436-- Notes:
2537--
@@ -93,6 +105,11 @@ module Streamly.Data.StreamK
93105 , take
94106
95107 -- * Combining Two Streams
108+ -- | Unlike the operations in "Streamly.Data.Stream", these operations can
109+ -- be used to dynamically compose large number of streams e.g. using the
110+ -- 'concatMapWith' and 'mergeMapWith' operations. They have a linear O(n)
111+ -- time complexity wrt to the number of streams being composed.
112+
96113 -- ** Appending
97114 , append
98115
@@ -116,6 +133,12 @@ module Streamly.Data.StreamK
116133 -- , CrossStreamK (..)
117134
118135 -- * Stream of streams
136+ -- | Some useful idioms:
137+ --
138+ -- >>> concatFoldableWith f = Prelude.foldr f StreamK.nil
139+ -- >>> concatMapFoldableWith f g = Prelude.foldr (f . g) StreamK.nil
140+ -- >>> concatForFoldableWith f xs g = Prelude.foldr (f . g) StreamK.nil xs
141+ --
119142 , concatEffect
120143 -- , concatMap
121144 , concatMapWith
@@ -168,9 +191,3 @@ import Prelude hiding (reverse, zipWith, mapM, dropWhile, take)
168191-- 1
169192-- 2
170193-- [1,2]
171−--
172−-- == Exception Handling
173−--
174−-- There are no native exception handling operations in the StreamK module,
175−-- please convert to 'Stream' type and use exception handling operations from
176−-- "Streamly.Data.Stream".
core/src/Streamly/Internal/Data/Stream/StreamD/Generate.hs+22−10
@@ -156,16 +156,6 @@ nil = Stream (\_ _ -> pure Stop) ()
156156
157157-- XXX implement in terms of consM?
158158-- cons x = consM (return x)
159−
160−-- | WARNING! You almost always need StreamK.'Streamly.Data.StreamK.cons'
161−-- instead.
162−--
163−-- Fuse a pure value at the head of an existing stream::
164−--
165−-- >>> s = 1 `Stream.cons` Stream.fromList [2,3]
166−-- >>> Stream.toList s
167−-- [1,2,3]
168−--
169159-- From an implementation perspective, StreamK.cons translates into a
170160-- functional call whereas fused cons translates into a conditional branch
171161-- (jump). However, the overhead of the function call in StreamK.cons only
@@ -173,6 +163,28 @@ nil = Stream (\_ _ -> pure Stop) ()
173163-- incurred for each subsequent element in the stream. As a result,
174164-- StreamK.cons has a time complexity of O(n), while fused cons has a time
175165-- complexity of O(n^2), where @n@ represents the number of 'cons' used.
166+
167+-- When composing a few elements together statically, a balanced tree composed
168+-- using 'cons' and 'append' is more efficient than a right associated one
169+-- composed using 'cons':
170+--
171+-- >>> s1 = 1 `Stream.cons` 2 `Stream.cons` Stream.nil
172+-- >>> s2 = 2 `Stream.cons` 3 `Stream.cons` Stream.nil
173+-- >>> s = s1 `Stream.append` s2
174+--
175+-- However, generating a stream using a case statement or indexing into a
176+-- static literal array would be the best. Check if the case statement
177+-- translates to a look up table or a binary search.
178+
179+-- | WARNING! O(n^2) time complexity wrt number of elements. Use the O(n)
180+-- complexity StreamK.'Streamly.Data.StreamK.cons' unless you want to
181+-- statically fuse just a few elements.
182+--
183+-- Fuse a pure value at the head of an existing stream::
184+--
185+-- >>> s = 1 `Stream.cons` Stream.fromList [2,3]
186+-- >>> Stream.toList s
187+-- [1,2,3]
176188--
177189-- Definition:
178190--
core/src/Streamly/Internal/Data/Stream/StreamD/Nesting.hs+24−21
@@ -211,14 +211,6 @@ import Prelude hiding (concatMap, mapM, zipWith)
211211
212212data AppendState s1 s2 = AppendFirst s1 | AppendSecond s2
213213
214−-- | Fuses two streams sequentially, yielding all elements from the first
215−-- stream, and then all elements from the second stream.
216−--
217−-- >>> s1 = Stream.fromList [1,2]
218−-- >>> s2 = Stream.fromList [3,4]
219−-- >>> Stream.fold Fold.toList $ s1 `Stream.append` s2
220−-- [1,2,3,4]
221−--
222214-- From an implementation perspective, StreamK.'Streamly.Data.StreamK.append'
223215-- translates into a function call whereas Stream.'append' translates into a
224216-- conditional branch (jump). However, the overhead of the function call in
@@ -227,10 +219,18 @@ data AppendState s1 s2 = AppendFirst s1 | AppendSecond s2
227219-- result, StreamK.append has a linear time complexity of O(n), while fused
228220-- append has a quadratic time complexity of O(n^2), where @n@ represents the
229221-- number of 'append's used.
222+
223+-- | WARNING! O(n^2) time complexity wrt number of streams. Suitable for
224+-- statically fusing a small number of streams. Use the O(n) complexity
225+-- StreamK.'Streamly.Data.StreamK.append' otherwise.
230226--
231−-- This function should only be used to statically fuse a stream with another
232−-- stream. Do not use it recursively or where it cannot be inlined. Use
233−-- StreamK.'Streamly.Data.StreamK.append' for those cases.
227+-- Fuses two streams sequentially, yielding all elements from the first
228+-- stream, and then all elements from the second stream.
229+--
230+-- >>> s1 = Stream.fromList [1,2]
231+-- >>> s2 = Stream.fromList [3,4]
232+-- >>> Stream.fold Fold.toList $ s1 `Stream.append` s2
233+-- [1,2,3,4]
234234--
235235{-# INLINE_NORMAL append #-}
236236append :: Monad m => Stream m a -> Stream m a -> Stream m a
@@ -261,14 +261,13 @@ append (Stream step1 state1) (Stream step2 state2) =
261261data InterleaveState s1 s2 = InterleaveFirst s1 s2 | InterleaveSecond s1 s2
262262 | InterleaveSecondOnly s2 | InterleaveFirstOnly s1
263263
264−-- | Interleaves two streams, yielding one element from each stream
265−-- alternately. When one stream stops the rest of the other stream is used in
266−-- the output stream.
264+-- | WARNING! O(n^2) time complexity wrt number of streams. Suitable for
265+-- statically fusing a small number of streams. Use the O(n) complexity
266+-- StreamK.'Streamly.Data.StreamK.interleave' otherwise.
267267--
268−-- When joining many streams in a left associative manner earlier streams will
269−-- get exponential priority than the ones joining later. Because of exponential
270−-- weighting it can be used with 'concatMapWith' even on a large number of
271−-- streams.
268+-- Interleaves two streams, yielding one element from each stream alternately.
269+-- When one stream stops the rest of the other stream is used in the output
270+-- stream.
272271--
273272{-# INLINE_NORMAL interleave #-}
274273interleave :: Monad m => Stream m a -> Stream m a -> Stream m a
@@ -507,7 +506,7 @@ roundRobin (Stream step1 state1) (Stream step2 state2) =
507506
508507-- | Like 'mergeBy' but with a monadic comparison function.
509508--
510−-- Merge two streams randomly:
509+-- Example, to merge two streams randomly:
511510--
512511-- @
513512-- > randomly _ _ = randomIO >>= \x -> return $ if x then LT else GT
@@ -515,7 +514,7 @@ roundRobin (Stream step1 state1) (Stream step2 state2) =
515514-- [2,1,2,2,2,1,1,1]
516515-- @
517516--
518−-- Merge two streams in a proportion of 2:1:
517+-- Example, merge two streams in a proportion of 2:1:
519518--
520519-- >>> :{
521520-- do
@@ -573,7 +572,11 @@ mergeByM cmp (Stream stepa ta) (Stream stepb tb) =
573572
574573 step _ (Nothing, Nothing, Nothing, Nothing) = return Stop
575574
576−-- | Merge two streams using a comparison function. The head elements of both
575+-- | WARNING! O(n^2) time complexity wrt number of streams. Suitable for
576+-- statically fusing a small number of streams. Use the O(n) complexity
577+-- StreamK.'Streamly.Data.StreamK.mergeBy' otherwise.
578+--
579+-- Merge two streams using a comparison function. The head elements of both
577580-- the streams are compared and the smaller of the two elements is emitted, if
578581-- both elements are equal then the element from the first stream is used
579582-- first.
core/src/Streamly/Internal/Data/Stream/StreamD/Type.hs+5−1
@@ -1105,7 +1105,11 @@ zipWithM f (Stream stepa ta) (Stream stepb tb) = Stream step (ta, tb, Nothing)
11051105{-# RULES "zipWithM xs xs"
11061106 forall f xs. zipWithM @Identity f xs xs = mapM (\x -> f x x) xs #-}
11071107
1108−-- | Stream @a@ is evaluated first, followed by stream @b@, the resulting
1108+-- | WARNING! O(n^2) time complexity wrt number of streams. Suitable for
1109+-- statically fusing a small number of streams. Use the O(n) complexity
1110+-- StreamK.'Streamly.Data.StreamK.zipWith' otherwise.
1111+--
1112+-- Stream @a@ is evaluated first, followed by stream @b@, the resulting
11091113-- elements @a@ and @b@ are then zipped using the supplied zip function and the
11101114-- result @c@ is yielded to the consumer.
11111115--
core/src/Streamly/Internal/Data/Stream/StreamK/Type.hs+7−24
@@ -305,7 +305,7 @@ nil = mkStream $ \_ _ _ stp -> stp
305305nilM :: Applicative m => m b -> StreamK m a
306306nilM m = mkStream $ \_ _ _ stp -> m *> stp
307307
308−-- | Create a singleton stream from a pure value.
308+-- Create a singleton stream from a pure value.
309309--
310310-- >>> fromPure a = a `StreamK.cons` StreamK.nil
311311-- >>> fromPure = pure
@@ -315,7 +315,7 @@ nilM m = mkStream $ \_ _ _ stp -> m *> stp
315315fromPure :: a -> StreamK m a
316316fromPure a = mkStream $ \_ _ single _ -> single a
317317
318−-- | Create a singleton stream from a monadic action.
318+-- Create a singleton stream from a monadic action.
319319--
320320-- >>> fromEffect m = m `StreamK.consM` StreamK.nil
321321--
@@ -825,7 +825,7 @@ foldl' step begin = foldlx' step begin id
825825-- an optimization opportunity that we can exploit.
826826-- drain = foldrM (\_ xs -> return () >> xs) (return ())
827827
828−-- |
828+--
829829-- > drain = foldl' (\_ _ -> ()) ()
830830-- > drain = mapM_ (\_ -> return ())
831831{-# INLINE drain #-}
@@ -856,18 +856,6 @@ null m =
856856
857857infixr 6 `append`
858858
859−-- | Appends two streams sequentially, yielding all elements from the first
860−-- stream, and then all elements from the second stream.
861−--
862−-- >>> s1 = StreamK.fromStream $ Stream.fromList [1,2]
863−-- >>> s2 = StreamK.fromStream $ Stream.fromList [3,4]
864−-- >>> Stream.fold Fold.toList $ StreamK.toStream $ s1 `StreamK.append` s2
865−-- [1,2,3,4]
866−--
867−-- This has O(n) append performance where @n@ is the number of streams. It can
868−-- be used to efficiently fold an infinite lazy container of streams using
869−-- 'concatMapWith' et. al.
870−--
871859{-# INLINE append #-}
872860append :: StreamK m a -> StreamK m a -> StreamK m a
873861-- XXX This doubles the time of toNullAp benchmark, may not be fusing properly
@@ -1633,14 +1621,10 @@ infixr 6 `interleave`
16331621-- variants of positional interleaving, e.g. run first stream for m seconds and
16341622-- run the second stream for n seconds.
16351623
1636−-- | Interleaves two streams, yielding one element from each stream
1637−-- alternately. When one stream stops the rest of the other stream is used in
1638−-- the output stream.
1639−--
1640−-- When joining many streams in a left associative manner earlier streams will
1641−-- get exponential priority than the ones joining later. Because of exponential
1642−-- weighting it can be used with 'concatMapWith' even on a large number of
1643−-- streams.
1624+-- | Note: When joining many streams in a left associative manner earlier
1625+-- streams will get exponential priority than the ones joining later. Because
1626+-- of exponentially high weighting of left streams it can be used with
1627+-- 'concatMapWith' even on a large number of streams.
16441628--
16451629{-# INLINE interleave #-}
16461630interleave :: StreamK m a -> StreamK m a -> StreamK m a
@@ -1965,7 +1949,6 @@ before action stream =
19651949 mkStream $ \st yld sng stp ->
19661950 action >> foldStreamShared st yld sng stp stream
19671951
1968−-- | concat . fromEffect
19691952{-# INLINE concatEffect #-}
19701953concatEffect :: Monad m => m (StreamK m a) -> StreamK m a
19711954concatEffect action =
core/src/Streamly/Internal/Data/StreamK.hs+25−10
@@ -233,10 +233,21 @@ import Streamly.Internal.Data.Parser (ParseError(..))
233233
234234#include "DocTestDataStreamK.hs"
235235
236+-- | Convert a fused 'Stream' to 'StreamK'.
237+--
238+-- For example:
239+--
240+-- >>> s1 = StreamK.fromStream $ Stream.fromList [1,2]
241+-- >>> s2 = StreamK.fromStream $ Stream.fromList [3,4]
242+-- >>> Stream.fold Fold.toList $ StreamK.toStream $ s1 `StreamK.append` s2
243+-- [1,2,3,4]
244+--
236245{-# INLINE fromStream #-}
237246fromStream :: Monad m => Stream.Stream m a -> StreamK m a
238247fromStream = Stream.toStreamK
239248
249+-- | Convert a 'StreamK' to a fused 'Stream'.
250+--
240251{-# INLINE toStream #-}
241252toStream :: Applicative m => StreamK m a -> Stream.Stream m a
242253toStream = Stream.fromStreamK
@@ -932,8 +943,9 @@ mapMaybe f = go
932943-- Exception Handling
933944-------------------------------------------------------------------------------
934945
935−-- | Like 'Streamly.Data.Stream.handle' but with one significant difference,
936−-- this function observes exceptions from the consumer of the stream as well.
946+-- | Like Streamly.Data.Stream.'Streamly.Data.Stream.handle' but with one
947+-- significant difference, this function observes exceptions from the consumer
948+-- of the stream as well.
937949--
938950-- You can also convert 'StreamK' to 'Stream' and use exception handling from
939951-- 'Stream' module:
@@ -969,10 +981,10 @@ handle f stream = go stream
969981-- the entire chain can be invoked when the stream ends voluntarily or if
970982-- someone decides to abandon the stream.
971983
972−-- | Like 'Streamly.Data.Stream.bracketIO' but with one significant difference,
973−-- this function observes exceptions from the consumer of the stream as well.
974−-- Therefore, it cleans up the resource promptly when the consumer encounters
975−-- an exception.
984+-- | Like Streamly.Data.Stream.'Streamly.Data.Stream.bracketIO' but with one
985+-- significant difference, this function observes exceptions from the consumer
986+-- of the stream as well. Therefore, it cleans up the resource promptly when
987+-- the consumer encounters an exception.
976988--
977989-- You can also convert 'StreamK' to 'Stream' and use resource handling from
978990-- 'Stream' module:
@@ -1013,14 +1025,13 @@ bracketIO bef aft bet =
10131025-- Serial Zipping
10141026------------------------------------------------------------------------------
10151027
1016−-- | Zip two streams serially using a pure zipping function.
1017−--
1028+-- | Zipping of @n@ streams can be performed by combining the streams pair
1029+-- wise using 'mergeMapWith' with O(n * log n) time complexity. If used
1030+-- with 'concatMapWith' it will have O(n^2) performance.
10181031{-# INLINE zipWith #-}
10191032zipWith :: Monad m => (a -> b -> c) -> StreamK m a -> StreamK m b -> StreamK m c
10201033zipWith f = zipWithM (\a b -> return (f a b))
10211034
1022−-- | Zip two streams serially using a monadic zipping function.
1023−--
10241035{-# INLINE zipWithM #-}
10251036zipWithM :: Monad m =>
10261037 (a -> b -> m c) -> StreamK m a -> StreamK m b -> StreamK m c
@@ -1110,6 +1121,10 @@ mergeByM cmp = go
11101121 _ -> yld x (goY rx y my)
11111122 in foldStream st yield single stop mx
11121123
1124+-- | Merging of @n@ streams can be performed by combining the streams pair
1125+-- wise using 'mergeMapWith' to give O(n * log n) time complexity. If used
1126+-- with 'concatMapWith' it will have O(n^2) performance.
1127+--
11131128{-# INLINE mergeBy #-}
11141129mergeBy :: (a -> a -> Ordering) -> StreamK m a -> StreamK m a -> StreamK m a
11151130-- XXX GHC: This has slightly worse performance than replacing "r <- cmp x y"