@@ -642,11 +642,16 @@ consMAsync m r = fromStream $ K.yieldM m `async` (toStream r)
642642-- AsyncT
643643------------------------------------------------------------------------------
644644
645−-- | The 'Semigroup' operation for 'AsyncT' appends two streams. The combined
646−-- stream behaves like a single stream with the actions from the second stream
647−-- appended to the first stream. The combined stream is evaluated in the
648−-- asynchronous style. This operation can be used to fold an infinite lazy
649−-- container of streams.
645+-- | The 'Semigroup' operation (@<>@) for 'AsyncT' merges two streams
646+-- concurrently with priority given to the first stream. In @s1 <> s2 <> s3
647+-- ...@ the streams s1, s2 and s3 are scheduled for execution in that order.
648+-- Multiple scheduled streams may be executed concurrently and the elements
649+-- generated by them are served to the consumer as and when they become
650+-- available. This behavior is similar to the scheduling and execution behavior
651+-- of actions in a single async stream.
652+--
653+-- Since only a finite number of streams are executed concurrently, this
654+-- operation can be used to fold an infinite lazy container of streams.
650655--
651656-- @
652657-- import "Streamly"
@@ -787,31 +792,79 @@ consMWAsync m r = fromStream $ K.yieldM m `wAsync` (toStream r)
787792wAsync :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
788793wAsync = joinStreamVarAsync WAsyncVar
789794
790−-- | The 'Semigroup' operation for 'WAsyncT' interleaves the elements from the
791−-- two streams. Therefore, when @a <> b@ is evaluated, one action is picked
792−-- from stream @a@ for evaluation and then the next action is picked from
793−-- stream @b@ and then the next action is again picked from stream @a@, going
794−-- around in a round-robin fashion. Many such actions are executed concurrently
795−-- depending on 'maxThreads' and 'maxBuffer' limits. Results are served to the
796−-- consumer in the order completion of the actions.
795+-- | 'WAsyncT' is similar to 'WSerialT' but with concurrent execution.
796+-- The 'Semigroup' operation (@<>@) for 'WAsyncT' merges two streams
797+-- concurrently interleaving the actions from both the streams. In @s1
798+-- <> s2 <> s3 ...@, the individual actions from streams @s1@, @s2@ and @s3@
799+-- are scheduled for execution in a round-robin fashion. Multiple scheduled
800+-- actions may be executed concurrently, the results from concurrent executions
801+-- are consumed in the order in which they become available.
802+--
797803--
798−-- Note that when multiple actions are combined like @a <> b <> c ... <> z@ we
799−-- go in a round-robin fasion across all of them picking one action from each
800−-- up to @z@ and then come back to @a@. Note that this operation cannot be
801−-- used to fold a container of infinite streams as the state that it needs to
802−-- maintain is proportional to the number of streams.
804+-- The @W@ in the name stands for @wide@ or breadth wise scheduling in
805+-- contrast to the depth wise scheduling behavior of 'AsyncT'.
803806--
804807-- @
805808-- import "Streamly"
806809-- import qualified "Streamly.Prelude" as S
807810-- import Control.Concurrent
808811--
809−-- main = (S.toList . 'wAsyncly' $ (S.fromList [1,2]) \<> (S.fromList [3,4])) >>= print
812+-- main = (S.toList . 'wAsyncly' . maxThreads 1 $ (S.fromList [1,2]) \<> (S.fromList [3,4])) >>= print
810813-- @
811814-- @
812815-- [1,3,2,4]
813816-- @
814817--
818+-- For this example, we are using @maxThreads 1@ so that concurrent thread
819+-- scheduling does not affect the results and make them unpredictable. Let's
820+-- now take a more general example:
821+--
822+-- @
823+-- main = (S.toList . 'wAsyncly' . maxThreads 1 $ (S.fromList [1,2,3]) \<> (S.fromList [4,5,6]) \<> (S.fromList [7,8,9])) >>= print
824+-- @
825+-- @
826+-- [1,4,2,7,5,3,8,6,9]
827+-- @
828+--
829+-- This is how the execution of the above stream proceeds:
830+--
831+-- 1. The scheduler queue is initialized with @[S.fromList [1,2,3],
832+-- (S.fromList [4,5,6]) \<> (S.fromList [7,8,9])]@ assuming the head of the
833+-- queue is represented by the rightmost item.
834+-- 2. @S.fromList [1,2,3]@ is executed, yielding the element @1@ and putting
835+-- @[2,3]@ at the back of the scheduler queue. The scheduler queue now looks
836+-- like @[(S.fromList [4,5,6]) \<> (S.fromList [7,8,9]), S.fromList [2,3]]@.
837+-- 3. Now @(S.fromList [4,5,6]) \<> (S.fromList [7,8,9])@ is picked up for
838+-- execution, @S.fromList [7,8,9]@ is added at the back of the queue and
839+-- @S.fromList [4,5,6]@ is executed, yielding the element @4@ and adding
840+-- @S.fromList [5,6]@ at the back of the queue. The queue now looks like
841+-- @[S.fromList [2,3], S.fromList [7,8,9], S.fromList [5,6]]@.
842+-- 4. Note that the scheduler queue expands by one more stream component in
843+-- every pass because one more @<>@ is broken down into two components. At this
844+-- point there are no more @<>@ operations to be broken down further and the
845+-- queue has reached its maximum size. Now these streams are scheduled in
846+-- round-robin fashion yielding @[2,7,5,3,8,8,9]@.
847+--
848+-- As we see above, in a right associated expression composed with @<>@, only
849+-- one @<>@ operation is broken down into two components in one execution,
850+-- therefore, if we have @n@ streams composed using @<>@ it will take @n@
851+-- scheduler passes to expand the whole expression. By the time @n-th@
852+-- component is added to the scheduler queue, the first component would have
853+-- received @n@ scheduler passes.
854+--
855+-- Since all streams get interleaved, this operation is not suitable for
856+-- folding an infinite lazy container of infinite size streams. However, if
857+-- the streams are small, the streams on the left may get finished before more
858+-- streams are added to the scheduler queue from the right side of the
859+-- expression, so it may be possible to fold an infinite lazy container of
860+-- streams. For example, if the streams are of size @n@ then at most @n@
861+-- streams would be in the scheduler queue at a time.
862+--
863+-- Note that 'WSerialT' and 'WAsyncT' differ in their scheduling behavior,
864+-- therefore the output of 'WAsyncT' even with a single thread of execution is
865+-- not the same as that of 'WSerialT' See notes in 'WSerialT' for details about
866+-- its scheduling behavior.
867+--
815868-- Any exceptions generated by a constituent stream are propagated to the
816869-- output stream. The output and exceptions from a single stream are guaranteed
817870-- to arrive in the same order in the resulting stream as they were generated