on master help sign in
Update the docs of async/wAsync/parallel
commit04afd6fcc10e7a5a93192c359cd5a7053016f378 authorHarendra Kumar <harendra@composewell.com> dateWed 31 Mar 2021, 23:18 (6 years ago) parent1f1e3bd
5 files changed+309−233
src/Streamly/Internal/Data/Stream/Ahead.hs+38−9 src/Streamly/Internal/Data/Stream/Async.hs+183−52 src/Streamly/Internal/Data/Stream/Parallel.hs+28−102 src/Streamly/Internal/Data/Stream/Serial.hs+16−11 src/Streamly/Prelude.hs+44−59
src/Streamly/Internal/Data/Stream/Ahead.hs+38−9
@@ -595,16 +595,43 @@ forkSVarAhead m1 m2 = mkStream $ \st yld sng stp -> do
595595
596596infixr 6 `ahead`
597597
598−-- | Appends two streams, some future actions may be evaluated concurrently,
599−-- ahead of time. The behavior is similar to 'consM' for 'AheadT' streams:
598+-- | Appends two streams, both the streams may be evaluated concurrently but
599+-- the outputs are used in the same order as the corresponding actions in the
600+-- original streams, side effects will happen in the order in which the streams
601+-- are evaluated:
602+--
603+-- >>> stream1 = Stream.yieldM (delay 4) -- SerialT IO Int
604+-- >>> stream2 = Stream.yieldM (delay 2) -- SerialT IO Int
605+-- >>> Stream.toList $ stream1 `ahead` stream2 -- IO [Int]
606+-- 2 sec
607+-- 4 sec
608+-- [4,2]
600609--
601−-- >>> stream = Stream.yieldM (delay 2) `ahead` Stream.yieldM (delay 1)
602−-- >>> Stream.toList stream -- IO [Int]
610+-- Multiple streams can be combined. With enough threads, all of them can be
611+-- scheduled simultaneously:
612+--
613+-- >>> stream3 = Stream.yieldM (delay 1)
614+-- >>> Stream.toList $ stream1 `ahead` stream2 `ahead` stream3
603615-- 1 sec
604616-- 2 sec
605−-- [2,1]
617+-- 4 sec
618+-- [4,2,1]
606619--
607−-- This operation can be used to fold an infinite lazy container of streams.
620+-- With 2 threads, only two can be scheduled at a time, when one of those
621+-- finishes, the third one gets scheduled:
622+--
623+-- >>> Stream.toList $ Stream.maxThreads 2 $ stream1 `ahead` stream2 `ahead` stream3
624+-- 2 sec
625+-- 1 sec
626+-- 4 sec
627+-- [4,2,1]
628+--
629+-- Only streams are scheduled for ahead evaluation, how actions within a stream
630+-- are evaluated depends on the stream type. If it is a concurrent stream they
631+-- will be evaluated concurrently. It may not make much sense combining serial
632+-- streams using 'ahead'.
633+--
634+-- 'ahead' can be safely used to fold an infinite lazy container of streams.
608635--
609636-- /Since: 0.3.0 ("Streamly")/
610637--
@@ -640,9 +667,6 @@ consMAhead m r = fromStream $ K.yieldM m `ahead` (toStream r)
640667-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.ahead'
641668-- @
642669--
643−-- Any exceptions generated by concurrent evaluation are propagated to the
644−-- output stream as soon as they occur.
645−--
646670-- A single 'Monad' bind behaves like a @for@ loop with iterations executed
647671-- concurrently, ahead of time, producing side effects of iterations out of
648672-- order, but results in order:
@@ -671,6 +695,11 @@ consMAhead m r = fromStream $ K.yieldM m `ahead` (toStream r)
671695-- 6 sec
672696-- [3,5,4,6]
673697--
698+-- The behavior can be explained as follows. All the iterations corresponding
699+-- to the element @1@ in the first stream constitute one output stream and all
700+-- the iterations corresponding to @2@ constitute another output stream and
701+-- these two output streams are merged using 'ahead'.
702+--
674703-- /Since: 0.3.0 ("Streamly")/
675704--
676705-- @since 0.8.0
src/Streamly/Internal/Data/Stream/Async.hs+183−52
@@ -634,9 +634,55 @@ joinStreamVarAsync style m1 m2 = mkStream $ \st yld sng stp ->
634634
635635infixr 6 `async`
636636
637−-- | Polymorphic version of the 'Semigroup' operation '<>' of 'AsyncT'.
638−-- Merges two streams possibly concurrently, preferring the
639−-- elements from the left one when available.
637+-- | Merges two streams, both the streams may be evaluated concurrently,
638+-- outputs from both are used as they arrive:
639+--
640+-- >>> stream1 = Stream.yieldM (delay 4)
641+-- >>> stream2 = Stream.yieldM (delay 2)
642+-- >>> Stream.toList $ stream1 `async` stream2
643+-- [4,2]
644+--
645+-- Multiple streams can be combined. With enough threads, all of them can be
646+-- scheduled simultaneously:
647+--
648+-- >>> stream3 = Stream.yieldM (delay 1)
649+-- >>> Stream.toList $ stream1 `async` stream2 `async` stream3
650+-- [1,2,4]
651+--
652+-- With 2 threads, only two can be scheduled at a time, when one of those
653+-- finishes, the third one gets scheduled:
654+--
655+-- >>> Stream.toList $ Stream.maxThreads 2 $ stream1 `async` stream2 `async` stream3
656+-- [2,1,4]
657+--
658+-- With a single thread, it becomes serial:
659+--
660+-- >>> Stream.toList $ Stream.maxThreads 1 $ stream1 `async` stream2 `async` stream3
661+-- [4,2,1]
662+--
663+-- Only streams are scheduled for async evaluation, how actions within a
664+-- stream are evaluated depends on the stream type. If it is a concurrent
665+-- stream they will be evaluated concurrently.
666+--
667+-- In the following example, both the streams are scheduled for concurrent
668+-- evaluation but each individual stream is evaluated serially:
669+--
670+-- >>> stream1 = Stream.fromListM $ map delay [3,3] -- SerialT IO Int
671+-- >>> stream2 = Stream.fromListM $ map delay [1,1] -- SerialT IO Int
672+-- >>> Stream.toList $ stream1 `async` stream2 -- IO [Int]
673+-- [1,1,3,3]
674+--
675+-- If total threads are 2, the third stream is scheduled only after one of the
676+-- first two has finished:
677+--
678+-- >>> stream3 = Stream.fromListM $ map delay [2,2] -- SerialT IO Int
679+-- >>> Stream.toList $ Stream.maxThreads 2 $ stream1 `async` stream2 `async` stream3 -- IO [Int]
680+-- [1,1,3,2,3,2]
681+--
682+-- Thus 'async' goes deep in first few streams rather than going wide in all
683+-- streams. It prefers to evaluate the leftmost streams as much as possible.
684+-- Because of this behavior, 'async' can be safely used to fold an infinite
685+-- lazy container of streams.
640686--
641687-- /Since: 0.2.0 ("Streamly")/
642688--
@@ -668,51 +714,45 @@ consMAsync m r = fromStream $ K.yieldM m `async` (toStream r)
668714-- AsyncT
669715------------------------------------------------------------------------------
670716
671−-- | The 'Semigroup' operation (@<>@) for 'AsyncT' merges two streams
672−-- concurrently with priority given to the first stream. In @s1 <> s2 <> s3
673−-- ...@ the streams s1, s2 and s3 are scheduled for execution in that order.
674−-- Multiple scheduled streams may be executed concurrently and the elements
675−-- generated by them are served to the consumer as and when they become
676−-- available. This behavior is similar to the scheduling and execution behavior
677−-- of actions in a single async stream.
678−--
679−-- Since only a finite number of streams are executed concurrently, this
680−-- operation can be used to fold an infinite lazy container of streams.
681−--
682−-- @
683−-- import qualified "Streamly.Prelude" as S
684−-- import Control.Concurrent
717+-- | For 'AsyncT' streams:
685718--
686−-- main = (S.toList . S.'asyncly' $ (S.fromList [1,2]) \<> (S.fromList [3,4])) >>= print
687−-- @
688719-- @
689−-- [1,2,3,4]
720+-- (<>) = 'Streamly.Prelude.async'
721+-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.async'
690722-- @
691723--
692−-- Any exceptions generated by a constituent stream are propagated to the
693−-- output stream. The output and exceptions from a single stream are guaranteed
694−-- to arrive in the same order in the resulting stream as they were generated
695−-- in the input stream. However, the relative ordering of elements from
696−-- different streams in the resulting stream can vary depending on scheduling
697−-- and generation delays.
724+-- A single 'Monad' bind behaves like a @for@ loop with iterations of the loop
725+-- executed concurrently a la the 'async' combinator, producing results and
726+-- side effects of iterations out of order:
698727--
699−-- Similarly, the monad instance of 'AsyncT' /may/ run each iteration
700−-- concurrently based on demand. More concurrent iterations are started only
701−-- if the previous iterations are not able to produce enough output for the
702−-- consumer.
728+-- >>> :{
729+-- Stream.toList $ Stream.asyncly $ do
730+-- x <- Stream.fromList [2,1] -- foreach x in stream
731+-- Stream.yieldM $ delay x
732+-- :}
733+-- 1 sec
734+-- 2 sec
735+-- [1,2]
703736--
704−-- @
705−-- main = S.'drain' . S.'asyncly' $ do
706−-- n <- return 3 \<\> return 2 \<\> return 1
707−-- S.yieldM $ do
708−-- threadDelay (n * 1000000)
709−-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
710−-- @
711−-- @
712−-- ThreadId 40: Delay 1
713−-- ThreadId 39: Delay 2
714−-- ThreadId 38: Delay 3
715−-- @
737+-- Nested monad binds behave like nested @for@ loops with nested iterations
738+-- executed concurrently, a la the 'async' combinator:
739+--
740+-- >>> :{
741+-- Stream.toList $ Stream.asyncly $ do
742+-- x <- Stream.fromList [1,2] -- foreach x in stream
743+-- y <- Stream.fromList [2,4] -- foreach y in stream
744+-- Stream.yieldM $ delay (x + y)
745+-- :}
746+-- 3 sec
747+-- 4 sec
748+-- 5 sec
749+-- 6 sec
750+-- [3,4,5,6]
751+--
752+-- The behavior can be explained as follows. All the iterations corresponding
753+-- to the element @1@ in the first stream constitute one output stream and all
754+-- the iterations corresponding to @2@ constitute another output stream and
755+-- these two output streams are merged using 'async'.
716756--
717757-- /Since: 0.1.0 ("Streamly")/
718758--
@@ -817,28 +857,119 @@ consMWAsync m r = fromStream $ K.yieldM m `wAsync` (toStream r)
817857
818858infixr 6 `wAsync`
819859
820−-- | Polymorphic version of the 'Semigroup' operation '<>' of 'WAsyncT'.
821−-- Merges two streams concurrently choosing elements from both fairly.
860+-- | For singleton streams, 'wAsync' is the same as 'async'. See 'async' for
861+-- singleton stream behavior. For multi-element streams, while 'async' is left
862+-- biased i.e. it tries to evaluate the left side stream as much as possible,
863+-- 'wAsync' tries to schedule them both fairly. In other words, 'async' goes
864+-- deep while 'wAsync' goes wide. However, outputs are always used as they
865+-- arrive.
866+--
867+-- With a single thread, 'async' starts behaving like 'serial' while 'wAsync'
868+-- starts behaving like 'wSerial'.
869+--
870+-- >>> stream1 = Stream.fromList [1,2,3]
871+-- >>> stream2 = Stream.fromList [4,5,6]
872+-- >>> Stream.toList $ Stream.asyncly $ Stream.maxThreads 1 $ stream1 `async` stream2
873+-- [1,2,3,4,5,6]
874+--
875+-- >>> Stream.toList $ Stream.wAsyncly $ Stream.maxThreads 1 $ stream1 `wAsync` stream2
876+-- [1,4,2,5,3,6]
877+--
878+-- With two threads available, and combining three streams:
879+--
880+-- >>> stream3 = Stream.fromList [7,8,9]
881+-- >>> Stream.toList $ Stream.asyncly $ Stream.maxThreads 2 $ stream1 `async` stream2 `async` stream3
882+-- [1,2,3,4,5,6,7,8,9]
883+--
884+-- >>> Stream.toList $ Stream.wAsyncly $ Stream.maxThreads 2 $ stream1 `wAsync` stream2 `wAsync` stream3
885+-- [1,4,2,7,5,3,8,6,9]
886+--
887+-- This operation cannot be used to fold an infinite lazy container of streams,
888+-- because it schedules all the streams in a round robin manner.
889+--
890+-- Note that 'WSerialT' and single threaded 'WAsyncT' both interleave streams
891+-- but the exact scheduling is slightly different in both cases.
822892--
823893-- @since 0.8.0
824894--
825895-- /Since: 0.2.0 ("Streamly")/
896+
897+-- Scheduling details:
898+--
899+-- This is how the execution of the above example proceeds:
900+--
901+-- 1. The scheduler queue is initialized with @[S.fromList [1,2,3],
902+-- (S.fromList [4,5,6]) \<> (S.fromList [7,8,9])]@ assuming the head of the
903+-- queue is represented by the rightmost item.
904+-- 2. @S.fromList [1,2,3]@ is executed, yielding the element @1@ and putting
905+-- @[2,3]@ at the back of the scheduler queue. The scheduler queue now looks
906+-- like @[(S.fromList [4,5,6]) \<> (S.fromList [7,8,9]), S.fromList [2,3]]@.
907+-- 3. Now @(S.fromList [4,5,6]) \<> (S.fromList [7,8,9])@ is picked up for
908+-- execution, @S.fromList [7,8,9]@ is added at the back of the queue and
909+-- @S.fromList [4,5,6]@ is executed, yielding the element @4@ and adding
910+-- @S.fromList [5,6]@ at the back of the queue. The queue now looks like
911+-- @[S.fromList [2,3], S.fromList [7,8,9], S.fromList [5,6]]@.
912+-- 4. Note that the scheduler queue expands by one more stream component in
913+-- every pass because one more @<>@ is broken down into two components. At this
914+-- point there are no more @<>@ operations to be broken down further and the
915+-- queue has reached its maximum size. Now these streams are scheduled in
916+-- round-robin fashion yielding @[2,7,5,3,8,6,9]@.
917+--
918+-- As we see above, in a right associated expression composed with @<>@, only
919+-- one @<>@ operation is broken down into two components in one execution,
920+-- therefore, if we have @n@ streams composed using @<>@ it will take @n@
921+-- scheduler passes to expand the whole expression. By the time @n-th@
922+-- component is added to the scheduler queue, the first component would have
923+-- received @n@ scheduler passes.
924+--
826925{-# INLINE wAsync #-}
827926wAsync :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
828927wAsync = joinStreamVarAsync WAsyncVar
829928
830−-- | 'WAsyncT' is similar to 'WSerialT' but with concurrent execution.
831−-- The 'Semigroup' operation (@<>@) for 'WAsyncT' merges two streams
832−-- concurrently interleaving the actions from both the streams. In @s1
833−-- <> s2 <> s3 ...@, the individual actions from streams @s1@, @s2@ and @s3@
834−-- are scheduled for execution in a round-robin fashion. Multiple scheduled
835−-- actions may be executed concurrently, the results from concurrent executions
836−-- are consumed in the order in which they become available.
929+-- | For 'WAsyncT' streams:
930+--
931+-- @
932+-- (<>) = 'Streamly.Prelude.wAsync'
933+-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.wAsync'
934+-- @
935+--
936+-- A single 'Monad' bind behaves like a @for@ loop with iterations of the loop
937+-- executed concurrently a la the 'wAsync' combinator, producing results and
938+-- side effects of iterations out of order:
837939--
940+-- >>> :{
941+-- Stream.toList $ Stream.wAsyncly $ do
942+-- x <- Stream.fromList [2,1] -- foreach x in stream
943+-- Stream.yieldM $ delay x
944+-- :}
945+-- 1 sec
946+-- 2 sec
947+-- [1,2]
948+--
949+-- Nested monad binds behave like nested @for@ loops with nested iterations
950+-- executed concurrently, a la the 'wAsync' combinator:
951+--
952+-- >>> :{
953+-- Stream.toList $ Stream.wAsyncly $ do
954+-- x <- Stream.fromList [1,2] -- foreach x in stream
955+-- y <- Stream.fromList [2,4] -- foreach y in stream
956+-- Stream.yieldM $ delay (x + y)
957+-- :}
958+-- 3 sec
959+-- 4 sec
960+-- 5 sec
961+-- 6 sec
962+-- [3,4,5,6]
963+--
964+-- The behavior can be explained as follows. All the iterations corresponding
965+-- to the element @1@ in the first stream constitute one 'WAsyncT' output
966+-- stream and all the iterations corresponding to @2@ constitute another
967+-- 'WAsyncT' output stream and these two output streams are merged using
968+-- 'wAsync'.
838969--
839970-- The @W@ in the name stands for @wide@ or breadth wise scheduling in
840971-- contrast to the depth wise scheduling behavior of 'AsyncT'.
841−--
972+
842973-- @
843974-- import qualified "Streamly.Prelude" as S
844975-- import Control.Concurrent
src/Streamly/Internal/Data/Stream/Parallel.hs+28−102
@@ -222,48 +222,27 @@ consMParallel m r = fromStream $ K.yieldM m `parallel` (toStream r)
222222
223223infixr 6 `parallel`
224224
225−-- | Execute two streams concurrently and merge their outputs. For example, if
226−-- stream @a@ is a serial stream consisting of @a1, a2, a3@ and stream @b@ is a
227−-- serial stream consisting of @b1, b2, b3@ then stream @a `parallel` b@ may
228−-- produce @a1, b1, a2, b2, a3, b3@ or @a1, a2, b1, a3, b2, b3@ or some other
229−-- combination depending on the rate at which the two streams produce elements.
230−-- However, the relative order of outputs from a single stream e.g. @a1, a2,
231−-- a3@ would remain the same in the resulting stream. The effects in the two
232−-- streams may occur concurrently.
233−--
234−-- To run single actions (instead of streams) in parallel wrap them into
235−-- singleton streams. The following trivial example is semantically equivalent
236−-- to running the action @putStrLn "hello"@ in the current thread:
237−--
238−-- >>> Stream.toList $ Stream.yieldM (putStrLn "hello") `Stream.parallel` Stream.nil
239−-- hello
240−-- [()]
241−--
242−-- Run two actions concurrently:
243−--
244−-- >>> import Control.Concurrent (threadDelay)
245−-- >>> Stream.toList $ Stream.yieldM (putStrLn "hello") `Stream.parallel` Stream.yieldM (threadDelay 100000 >> putStrLn "world")
246−-- hello
247−-- world
248−-- [(),()]
249−--
250−-- Run effects concurrently, disregarding their outputs:
251−--
252−-- >>> Stream.toList $ nilM (putStrLn "hello") `parallel` Stream.nilM (threadDelay 100000 >> putStrLn "world")
253−-- hello
254−-- world
255−-- []
256−--
257−-- Run an effectful action, and a pure effect without any output, concurrently:
258−--
259−-- >>> Stream.toList $ Stream.yieldM (return 1) `Stream.parallel` Stream.nilM (putStrLn "world")
260−-- world
261−-- [1]
262−--
263−-- Note that 'parallel' is a polymorphic version of the @Semigroup@ operation
264−-- @<>@ of 'ParallelT'.
265−--
266−-- `nilM` is currently @Internal@.
225+-- | Like 'Streamly.Prelude.async' except that the execution is much more
226+-- strict. There is no limit on the number of threads. While
227+-- 'Streamly.Prelude.async' may not schedule a stream if there is no demand
228+-- from the consumer, 'parallel' always evaluates both the streams immediately.
229+-- The only limit that applies to 'parallel' is 'Streamly.Prelude.maxBuffer'.
230+-- Evaluation may block if the output buffer becomes full.
231+--
232+-- >>> stream = Stream.yieldM (delay 2) `parallel` Stream.yieldM (delay 1)
233+-- >>> Stream.toList stream -- IO [Int]
234+-- 1 sec
235+-- 2 sec
236+-- [1,2]
237+--
238+-- 'parallel' guarantees that all the streams are scheduled for execution
239+-- immediately, therefore, we could use things like starting timers inside the
240+-- streams and relying on the fact that all timers were started at the same
241+-- time.
242+--
243+-- Unlike 'async' this operation cannot be used to fold an infinite lazy
244+-- container of streams, because it schedules all the streams strictly
245+-- concurrently.
267246--
268247-- /Since: 0.2.0 ("Streamly")/
269248--
@@ -371,7 +350,7 @@ mkParallel = D.fromStreamD . mkParallelD . D.toStreamD
371350--
372351-- | Redirect a copy of the stream to a supplied fold and run it concurrently
373352-- in an independent thread. The fold may buffer some elements. The buffer size
374−-- is determined by the prevailing 'maxBuffer' setting.
353+-- is determined by the prevailing 'Streamly.Prelude.maxBuffer' setting.
375354--
376355-- @
377356-- Stream m a -> m b
@@ -481,69 +460,16 @@ distributeAsync_ = flip (foldr tapAsync)
481460-- ParallelT
482461------------------------------------------------------------------------------
483462
484−-- | Async composition with strict concurrent execution of all streams.
485−--
486−-- The 'Semigroup' instance of 'ParallelT' executes both the streams
487−-- concurrently without any delay or without waiting for the consumer demand
488−-- and /merges/ the results as they arrive. If the consumer does not consume
489−-- the results, they are buffered upto a configured maximum, controlled by the
490−-- 'maxBuffer' primitive. If the buffer becomes full the concurrent tasks will
491−-- block until there is space in the buffer.
492−--
493−-- Both 'WAsyncT' and 'ParallelT', evaluate the constituent streams fairly in a
494−-- round robin fashion. The key difference is that 'WAsyncT' might wait for the
495−-- consumer demand before it executes the tasks whereas 'ParallelT' starts
496−-- executing all the tasks immediately without waiting for the consumer demand.
497−-- For 'WAsyncT' the 'maxThreads' limit applies whereas for 'ParallelT' it does
498−-- not apply. In other words, 'WAsyncT' can be lazy whereas 'ParallelT' is
499−-- strict.
500−--
501−-- 'ParallelT' is useful for cases when the streams are required to be
502−-- evaluated simultaneously irrespective of how the consumer consumes them e.g.
503−-- when we want to race two tasks and want to start both strictly at the same
504−-- time or if we have timers in the parallel tasks and our results depend on
505−-- the timers being started at the same time. If we do not have such
506−-- requirements then 'AsyncT' or 'AheadT' are recommended as they can be more
507−-- efficient than 'ParallelT'.
463+-- | For 'ParallelT' streams:
508464--
509465-- @
510−-- main = (S.'toList' . S.'parallely' $ (S.fromFoldable [1,2]) \<> (S.fromFoldable [3,4])) >>= print
511−-- @
512−-- @
513−-- [1,3,2,4]
514−-- @
515−--
516−-- When streams with more than one element are merged, it yields whichever
517−-- stream yields first without any bias, unlike the 'Async' style streams.
518−--
519−-- Any exceptions generated by a constituent stream are propagated to the
520−-- output stream. The output and exceptions from a single stream are guaranteed
521−-- to arrive in the same order in the resulting stream as they were generated
522−-- in the input stream. However, the relative ordering of elements from
523−-- different streams in the resulting stream can vary depending on scheduling
524−-- and generation delays.
525−--
526−-- Similarly, the 'Monad' instance of 'ParallelT' runs /all/ iterations
527−-- of the loop concurrently.
528−--
529−-- @
530−-- import qualified "Streamly.Prelude" as S
531−-- import Control.Concurrent
532−--
533−-- main = S.'drain' . S.'parallely' $ do
534−-- n <- return 3 \<\> return 2 \<\> return 1
535−-- S.yieldM $ do
536−-- threadDelay (n * 1000000)
537−-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
538−-- @
539−-- @
540−-- ThreadId 40: Delay 1
541−-- ThreadId 39: Delay 2
542−-- ThreadId 38: Delay 3
466+-- (<>) = 'Streamly.Prelude.parallel'
467+-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.parallel'
543468-- @
544469--
545−-- Note that parallel composition can only combine a finite number of
546−-- streams as it needs to retain state for each unfinished stream.
470+-- See 'Streamly.Prelude.AsyncT', 'ParallelT' is similar except that all
471+-- iterations are strictly concurrent while in 'AsyncT' it depends on the
472+-- consumer demand and available threads. See 'parallel' for more details.
547473--
548474-- /Since: 0.1.0 ("Streamly")/
549475--
src/Streamly/Internal/Data/Stream/Serial.hs+16−11
@@ -90,8 +90,8 @@ import Prelude hiding (map, mapM, errorWithoutStackTrace)
9090-- | For 'SerialT' streams:
9191--
9292-- @
93−-- (<>) = 'Streamly.Prelude.serial'
94−-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.serial'
93+-- (<>) = 'Streamly.Prelude.serial' -- 'Semigroup'
94+-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.serial' -- 'Monad'
9595-- @
9696--
9797-- A single 'Monad' bind behaves like a @for@ loop:
@@ -251,8 +251,8 @@ TRAVERSABLE_INSTANCE(SerialT)
251251-- | For 'WSerialT' streams:
252252--
253253-- @
254−-- (<>) = 'Streamly.Prelude.wSerial'
255−-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.wSerial'
254+-- (<>) = 'Streamly.Prelude.wSerial' -- 'Semigroup'
255+-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.wSerial' -- 'Monad'
256256-- @
257257--
258258-- Note that '<>' is associative only if we disregard the ordering of elements
@@ -360,6 +360,18 @@ infixr 6 `wSerial`
360360-- >>> Stream.toList $ Stream.wSerially $ stream1 `wSerial` stream2
361361-- [1,3,2,4]
362362--
363+-- Note, for singleton streams 'wSerial' and 'serial' are identical.
364+--
365+-- Note that this operation cannot be used to fold a container of infinite
366+-- streams but it can be used for very large streams as the state that it needs
367+-- to maintain is proportional to the logarithm of the number of streams.
368+--
369+-- @since 0.8.0
370+--
371+-- /Since: 0.2.0 ("Streamly")/
372+
373+-- Scheduling Notes:
374+--
363375-- Note that evaluation of @a \`wSerial` b \`wSerial` c@ does not interleave
364376-- @a@, @b@ and @c@ with equal priority. This expression is equivalent to @a
365377-- \`wSerial` (b \`wSerial` c)@, therefore, it fairly interleaves @a@ with the
@@ -369,13 +381,6 @@ infixr 6 `wSerial`
369381-- priority as the rest of the streams taken together. The same is true for
370382-- each subexpression on the right.
371383--
372−-- Note that this operation cannot be used to fold a container of infinite
373−-- streams but it can be used for very large streams as the state that it needs
374−-- to maintain is proportional to the logarithm of the number of streams.
375−--
376−-- @since 0.8.0
377−--
378−-- /Since: 0.2.0 ("Streamly")/
379384{-# INLINE wSerial #-}
380385wSerial :: IsStream t => t m a -> t m a -> t m a
381386wSerial m1 m2 = mkStream $ \st yld sng stp -> do
src/Streamly/Prelude.hs+44−59
@@ -33,14 +33,27 @@
3333-- >>> Stream.toList stream -- IO [Int]
3434-- [1,2]
3535--
36−-- 'consM' constructs an effectful stream:
36+-- To demonstrate the serial vs. concurrent execution behavior we will use the
37+-- following blocking IO action that blocks for @n@ seconds:
38+--
39+-- >>> import Control.Concurrent (threadDelay)
40+-- >>> :{
41+-- delay n = do
42+-- threadDelay (n * 1000000) -- sleep for n seconds
43+-- putStrLn (show n ++ " sec") -- print "n sec"
44+-- return n -- IO Int
45+-- :}
46+-- >>> delay 1
47+-- 1 sec
48+--
49+-- 'consM' constructs a stream from effectful actions:
3750--
3851-- @
39−-- > stream = 'getLine' \`consM` 'getLine' \`consM` 'nil'
52+-- > stream = delay 1 \`consM` delay 2 \`consM` 'nil'
4053-- > Stream.'toList' stream
41−-- hello
42−-- world
43−-- ["hello","world"]
54+-- 1 sec
55+-- 2 sec
56+-- [1,2]
4457-- @
4558--
4659-- In the following example, 'repeatM' generates an infinite stream of 'String'
@@ -68,17 +81,6 @@
6881-- actions forming the stream are executed. Below we will see how 'SerialT',
6982-- 'AsyncT' and 'AheadT' stream types differ from each other.
7083--
71−-- To demonstrate the serial vs. concurrent execution behavior we will use the
72−-- following blocking action that adds a delay of @n@ seconds:
73−--
74−-- >>> import Control.Concurrent (threadDelay)
75−-- >>> :{
76−-- delay n = do
77−-- threadDelay (n * 1000000) -- sleep for n seconds
78−-- putStrLn (show n ++ " sec") -- print "n sec"
79−-- return n -- IO Int
80−-- :}
81−--
8284-- 'SerialT' executes actions serially, so the total delay in the following
8385-- example is @2 + 1 = 3@ seconds:
8486--
@@ -174,11 +176,12 @@
174176--
175177-- >>> stream1 = Stream.fromListM [delay 1, delay 2]
176178-- >>> stream2 = Stream.fromListM [delay 3, delay 4]
177−-- >>> Stream.drain $ stream1 <> stream2
179+-- >>> Stream.toList $ stream1 <> stream2
178180-- 1 sec
179181-- 2 sec
180182-- 3 sec
181183-- 4 sec
184+-- [1,2,3,4]
182185--
183186-- For 'WSerialT', '<>' has an interleaving behavior i.e. it executes one
184187-- action from the first stream and then one action from the second stream and
@@ -186,11 +189,12 @@
186189--
187190-- >>> stream1 = Stream.fromListM [delay 1, delay 2]
188191-- >>> stream2 = Stream.fromListM [delay 3, delay 4]
189−-- >>> Stream.drain $ Stream.wSerially $ stream1 <> stream2
192+-- >>> Stream.toList $ Stream.wSerially $ stream1 <> stream2
190193-- 1 sec
191194-- 3 sec
192195-- 2 sec
193196-- 4 sec
197+-- [1,3,2,4]
194198--
195199-- The '<>' operation of 'SerialT' and 'WSerialT' is the same as 'serial' and
196200-- 'wSerial' respectively. The 'serial' combinator combines two streams of any
@@ -204,13 +208,14 @@
204208-- Let's take an example of the `parallel` combinator ('<>' operation of
205209-- 'ParallelT'), it schedules both the streams concurrently.
206210--
207−-- >>> stream1 = Stream.fromListM [delay 1, delay 2]
208−-- >>> stream2 = Stream.fromListM [delay 3, delay 4]
209−-- >>> Stream.drain $ stream1 `parallel` stream2
211+-- >>> stream1 = Stream.fromListM [delay 3, delay 4]
212+-- >>> stream2 = Stream.fromListM [delay 1, delay 2]
213+-- >>> Stream.toList $ stream1 `parallel` stream2
210214-- 1 sec
211215-- 2 sec
212216-- 3 sec
213217-- 4 sec
218+-- [1,2,3,4]
214219--
215220-- We can use @concatMapWith parallel@ to read concurrently from all incoming
216221-- network connections and combine the input streams into a single output
@@ -768,7 +773,14 @@ module Streamly.Prelude
768773 -- ** Interleaving
769774 , wSerial
770775
771− -- ** Scheduling
776+ -- ** Concurrent Scheduling
777+ -- | Any exceptions generated by concurrent evaluation are propagated to
778+ -- the consumer of the stream as soon as they occur. Exceptions from a
779+ -- particular stream are guaranteed to arrive in the same order in the
780+ -- output stream as they were generated in the input stream.
781+ --
782+ -- See 'maxThreads' and 'maxBuffer' to control the concurrency of these
783+ -- combinators.
772784 , ahead
773785 , async
774786 , wAsync
@@ -919,43 +931,16 @@ import Streamly.Internal.Data.Stream.IsStream
919931
920932-- $async
921933--
922−-- /Scheduling and execution:/ In an asynchronous stream @a \`consM` b \`consM`
923−-- c ...@, the actions @a@, @b@, and @c@ are executed concurrently with the
924−-- consumer of the stream. The actions are /scheduled/ for execution in the
925−-- same order as they are specified in the stream. Multiple scheduled actions
926−-- may be /executed/ concurrently in parallel threads of execution. The
927−-- actions may be executed out of order and they may complete at arbitrary
928−-- times. Therefore, the /effects/ of the actions may be observed out of
929−-- order.
930−--
931−-- /Buffering:/ The /results/ from multiple threads of execution are queued in
932−-- a buffer as soon as they become available. The consumer of the stream is
933−-- served from this buffer. Therefore, the consumer may observe the results to
934−-- be out of order. In other words, an asynchronous stream is an unordered
935−-- stream i.e. order does not matter.
936−--
937−-- /Concurrency control:/ Threads are suspended if the `maxBuffer` limit is
938−-- reached, and resumed when the consumer makes space in the buffer. The
939−-- maximum number of concurrent threads depends on `maxThreads`. Number of
940−-- threads is increased or decreased based on the speed of the consumer.
941−--
942−-- /Generation operations:/ Concurrent stream generation operations e.g.
943−-- 'Streamly.Prelude.replicateM' when used in async style schedule and execute
944−-- the stream generating actions in the manner described above. The generation
945−-- actions run concurrently, effects and results of the actions as observed by
946−-- the consumer of the stream may be out of order.
947−--
948−-- /Transformation operations:/ Concurrent stream transformation operations
949−-- e.g. 'Streamly.Prelude.mapM', when used in async style, schedule and
950−-- execute transformation actions in the manner described above. Transformation
951−-- actions run concurrently, effects and results of the actions may be
952−-- observed by the consumer out of order.
953−--
954−-- /Variants:/ There are two asynchronous stream types 'AsyncT' and 'WAsyncT'.
955−-- They are identical with respect to single stream evaluation behavior. Their
956−-- behaviors differ in how they combine multiple streams using 'Semigroup' or
957−-- 'Monad' composition. Since the order of elements does not matter in
958−-- asynchronous streams the 'Semigroup' operation is effectively commutative.
934+-- Asynchronous streams evaluate some future actions concurrently, the results
935+-- are given to the consumer as soon as they become available, they may not be
936+-- in the same order as the actions in the stream.
937+--
938+-- The results of the actions as well as their side effects are partially
939+-- ordered. Since the order of elements does not matter the 'Semigroup'
940+-- operation is effectively commutative.
941+--
942+-- There are two asynchronous stream types 'AsyncT' and 'WAsyncT'. They differ
943+-- only in the 'Semigroup' and 'Monad' instance behavior.
959944
960945-- $zipping
961946--