on master help sign in
streamly/docs/User/Tutorials/ConcurrentStreams.hs
771 lines 31 kB history raw
1{-# OPTIONS_GHC -fno-warn-unused-imports #-}
2-- |
3-- Module : User.Tutorials.ConcurrentStreams
4-- Copyright : (c) 2017 Composewell Technologies
5--
6-- License : BSD3
7-- Maintainer : streamly@composewell.com
8--
9-- THIS TUTORIAL IS OBSOLETE.
10--
11-- In this tutorial we will show how streamly can be used for idiomatic and
12-- declarative concurrent programming. Before you go through this tutorial we
13-- recommend that you take a look at the Streamly serial streams tutorial.
15module User.Tutorials.ConcurrentStreams
16 (
17 -- * Imports
18 -- $setup
20 -- * Concurrent Streams
21 -- $concurrentStreams
23 -- * Combining Streams
24 -- $flavors
26 -- * Imports and Supporting Code
27 -- $imports
29 -- * Generating Streams Concurrently
30 -- $generatingConcurrently
32 -- * Concurrent Pipeline Stages
33 -- $concurrentApplication
35 -- * Mapping Concurrently
36 -- $concurrentTransformation
38 -- * Merging Streams
40 -- ** Semigroup Style
42 -- *** Deep Speculative Composition ('Ahead')
43 -- $ahead
45 -- *** Deep Asynchronous Composition ('Async')
46 -- $async
48 -- *** Wide Asynchronous Composition ('WAsync')
49 -- $wasync
51 -- *** Parallel Asynchronous Composition ('Parallel')
52 -- $parallel
54 -- XXX we should deprecate and remove the mkAsync API
55 -- Custom composition
56 -- custom
58 -- ** Monoid Style
59 -- $monoid
61 -- * Nesting Streams
63 -- ** Monad
64 -- *** Deep Speculative Nesting ('Ahead')
65 -- $aheadNesting
67 -- *** Deep Asynchronous Nesting ('Async')
68 -- $concurrentNesting
70 -- *** Wide Asynchronous Nesting ('WAsync')
71 -- $wasyncNesting
73 -- *** Parallel Asynchronous Nesting ('Parallel')
74 -- $parallelNesting
76 -- ** Applicative
77 -- $applicative
79 -- * Zipping Streams
81 -- ** Parallel Zipping
82 -- $parallelzip
84 -- * Concurrent Programming
85 -- $concurrent
87 -- * Writing Concurrent Programs
88 -- $programs
90 -- * Where to go next?
91 -- $furtherReading
92 )
93where
95import Streamly.Data.Stream
96import Data.Semigroup
97import Control.Applicative
98import Control.Monad
99import Control.Monad.IO.Class (MonadIO(..))
100import Control.Monad.Trans.Class (MonadTrans (lift))
102-- CAUTION: please keep setup and imports sections in sync
104-- XXX This tutorial has to be rewritten.
106-- $setup
107-- In most of example snippets we do not repeat the imports. Where imports are
108-- not explicitly specified use the imports shown below.
109--
110-- >>> :m
111-- >>> :set -fno-warn-deprecations
112-- >>> import Data.Function ((&))
113-- >>> import Streamly.Prelude ((|:), (|&))
114-- >>> import Streamly.Internal.Data.Stream.Cross (CrossStream(..))
115-- >>> import qualified Streamly.Prelude as Stream
116-- >>> import qualified Streamly.Data.Fold as Fold
117--
118-- To illustrate concurrent vs serial composition aspects, we will use the
119-- following @delay@ function to introduce a sleep or delay specified in
120-- seconds. After the delay it prints the number of seconds it slept.
121--
122-- >>> import Control.Concurrent (threadDelay, myThreadId)
123-- >>> :{
124-- delay n = Stream.fromEffect $ do
125-- threadDelay (n * 1000000)
126-- tid <- myThreadId
127-- putStrLn (show tid ++ ": Delay " ++ show n)
128-- :}
129--
130-- For concurrent examples, use line buffering, otherwise output from different
131-- threads may get mixed:
132--
133-- >>> import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
134-- >>> hSetBuffering stdout LineBuffering
135--
137-- $concurrentStreams
138--
139-- Many stream operations can be done concurrently:
140--
141-- * Streams can be generated concurrently.
142--
143-- * Streams can be merged concurrently.
144--
145-- * Multiple stages in a streaming pipeline can run concurrently.
146--
147-- * Streams can be mapped and zipped concurrently.
148--
149-- * In monadic composition they combine like a list transformer,
150-- providing concurrent non-determinism.
151--
152-- There are three basic concurrent stream styles, 'Ahead', 'Async', and
153-- 'Parallel'. The 'Ahead' style streams are similar to 'Serial' except that
154-- they can speculatively execute multiple stream actions concurrently in
155-- advance. 'Ahead' would return exactly the same stream as 'Serial' except
156-- that it may execute the actions concurrently. The 'Async' style streams,
157-- like 'Ahead', speculatively execute multiple stream actions in advance but
158-- return the results in their finishing order rather than in the stream
159-- traversal order. 'Parallel' is like 'Async' except that it provides
160-- unbounded parallelism instead of controlled parallelism.
161--
162-- For easy reference, we can classify the stream types based on /execution order/,
163-- /consumption order/, and /bounded or unbounded/ concurrency.
164-- Execution could be serial (i.e. synchronous) or asynchronous. In serial
165-- execution we execute the next action in the stream only after the previous
166-- one has finished executing. In asynchronous execution multiple actions in
167-- the stream can be executed asynchronously i.e. the next action can start
168-- executing even before the first one has finished. Consumption order
169-- determines the order in which the outputs generated by the composition are
170-- consumed. Consumption could be serial or asynchronous. In serial
171-- consumption, the outputs are consumed in the traversal order, in
172-- asynchronous consumption the outputs are consumed as they arrive i.e. first
173-- come first serve order.
174--
175-- +------------+--------------+--------------+--------------+
176-- | Type | Execution | Consumption | Concurrency |
177-- +============+==============+==============+==============+
178-- | 'Serial' | Serial | Serial | None |
179-- +------------+--------------+--------------+--------------+
180-- | 'Ahead' | Asynchronous | Serial | bounded |
181-- +------------+--------------+--------------+--------------+
182-- | 'Async' | Asynchronous | Asynchronous | bounded |
183-- +------------+--------------+--------------+--------------+
184-- | 'Parallel' | Asynchronous | Asynchronous | unbounded |
185-- +------------+--------------+--------------+--------------+
186--
187-- All these types can be freely inter-converted using type conversion
188-- combinators or type annotations, without any cost, to achieve the desired
189-- composition style. To force a particular type of composition, we coerce the
190-- stream type using the corresponding type adapting combinator from
191-- 'fromSerial', 'fromAhead', 'fromAsync', or 'fromParallel'. The default stream type
192-- is inferred as 'Serial' unless you change it by using one of the combinators
193-- or by using a type annotation.
195-- $flavors
196--
197-- Streams can be combined using '<>' or 'mappend' to form a
198-- composite. Composite streams can be interpreted in a depth first or
199-- breadth first manner using an appropriate type conversion before
200-- consumption. Deep (e.g. 'Serial') stream type variants traverse a
201-- composite stream in a depth first manner, such that each stream is
202-- traversed fully before traversing the next stream. Wide
203-- (e.g. 'WSerial') stream types traverse it in a breadth first
204-- manner, such that one element from each stream is traversed before
205-- coming back to the first stream again.
206--
207-- Each stream type has a wide traversal variant prefixed by 'W'. The wide
208-- variant differs only in the Semigroup\/Monoid, Applicative\/Monad
209-- compositions of the streams.
210-- The following table summarizes the basic types and the corresponding wide
211-- variants:
212--
213-- @
214-- +------------+-----------+
215-- | Deep | Wide |
216-- +============+===========+
217-- | 'Serial' | 'WSerial' |
218-- +------------+-----------+
219-- | 'Ahead' | 'WAhead' |
220-- +------------+-----------+
221-- | 'Async' | 'WAsync' |
222-- +------------+-----------+
223-- @
224--
225-- Other than these types there are also 'ZipSerial' and 'ZipAsync' types that
226-- zip streams serially or concurrently using 'Applicative' operation. These
227-- types are not monads they are only applicatives and they do not differ in
228-- 'Semigroup' composition.
229--
231-- $programs
232--
233-- When writing concurrent programs it is advised to not use the concurrent
234-- style stream combinators blindly at the top level. That might create too
235-- much concurrency where it is not even required, and can even degrade
236-- performance in some cases. In some cases it can also lead to surprising
237-- behavior because of some code that is supposed to be serial becoming
238-- concurrent. Please be aware that all concurrency capable APIs that you may
239-- have used under the scope of a concurrent stream combinator will become
240-- concurrent. For example if you have a 'repeatM' somewhere in your program
241-- and you use 'fromParallel' on top, the 'repeatM' becomes fully parallel,
242-- resulting into an infinite parallel execution . Instead, use the
243-- /Keep It Serial and Stupid/ principle, start with the default serial
244-- composition and enable concurrent combinators only when and where necessary.
245-- When you use a concurrent combinator you can use an explicit 'fromSerial'
246-- combinator to suppress any unnecessary concurrency under the scope of that
247-- combinator.
249-- $generatingConcurrently
250--
251-- Monadic construction and generation functions like 'consM', 'unfoldrM',
252-- 'replicateM', 'repeatM', 'iterateM' and 'fromFoldableM' work concurrently
253-- when used with appropriate stream type combinator. The pure versions of
254-- these APIs are not concurrent, however you can use the monadic versions even
255-- for pure computations by wrapping the pure value in a monad to get the
256-- concurrent generation capability where required.
257--
258-- The following code finishes in 3 seconds (6 seconds when serial):
259--
260-- >>> let p n = threadDelay (n * 1000000) >> return n
261-- >>> Stream.toList $ Stream.fromParallel $ p 3 |: p 2 |: p 1 |: Stream.nil
262-- [1,2,3]
263--
264-- >>> Stream.toList $ Stream.fromAhead $ p 3 |: p 2 |: p 1 |: Stream.nil
265-- [3,2,1]
266--
267-- The following finishes in 10 seconds (100 seconds when serial):
268--
269-- >>> Stream.drain $ Stream.fromAsync $ Stream.replicateM 10 $ p 10
270--
272-- $concurrentTransformation
273--
274-- Monadic transformation functions 'mapM' and 'sequence' work concurrently
275-- when used with appropriate stream type combinators. The pure versions do not
276-- work concurrently, however you can use the monadic versions even for pure
277-- computations to get the concurrent transformation capability where required.
278--
279-- This would print a value every second (2 seconds when serial):
280--
281-- >>> let p n = threadDelay (n * 1000000) >> return n
282-- >>> :{
283-- parMap =
284-- Stream.repeatM (p 1)
285-- & Stream.fromSerial -- repeatM is serial
286-- & Stream.mapM (\x -> p 1 >> print x)
287-- & Stream.fromAhead -- mapM is cocnurrent using Ahead style
288-- & Stream.drain
289-- :}
290--
292-- $concurrentApplication
293--
294-- The concurrent function application operators '|$' and '|&' apply a stream
295-- argument to a stream function concurrently to compose a concurrent pipeline
296-- of stream processing functions:
297--
298-- Because both the stages run concurrently, we would see a delay of only 1
299-- second instead of 2 seconds in the following:
300--
301-- >>> let p n = threadDelay (n * 1000000) >> return n
302-- >>> :{
303-- parApp =
304-- Stream.repeatM (p 1)
305-- |& Stream.mapM (\x -> p 1 >> print x)
306-- & Stream.drain
307-- :}
309-- $ahead
310--
311-- The 'Semigroup' operation '<>' of the 'Ahead' type combines two streams in a
312-- /serial depth first/ manner with concurrent lookahead. We use the 'fromAhead'
313-- type combinator to effect 'Ahead' style of composition. We can also use an
314-- explicit 'Ahead' type annotation for the stream to achieve the same effect.
315--
316-- When two streams are combined in this manner, the streams are traversed in
317-- depth first manner just like 'Serial', however it can execute the next
318-- stream concurrently and keep the results ready when its turn arrives.
319-- Concurrent execution of the next stream(s) is performed if the first stream
320-- blocks or if it cannot produce output at the rate that is enough to meet the
321-- consumer demand. Multiple streams can be executed concurrently to meet the
322-- demand. The following example would print the result in a second even
323-- though each action in each stream takes one second:
324--
325-- >>> p n = threadDelay 1000000 >> return n
326-- >>> stream1 = p 1 |: p 2 |: Stream.nil
327-- >>> stream2 = p 3 |: p 4 |: Stream.nil
328-- >>> Stream.toList $ Stream.fromAhead $ stream1 <> stream2
329-- [1,2,3,4]
330--
331-- Each stream is constructed 'fromAhead' and then both the streams are merged
332-- 'fromAhead', therefore, all the actions can run concurrently but the result is
333-- presented in serial order.
334--
335-- You can also use the polymorphic combinator 'ahead' in place of '<>' to
336-- compose any type of streams in this manner.
338-- $async
339--
340-- The 'Semigroup' operation '<>' of the 'Async' type combines the two
341-- streams in a depth first manner with parallel look ahead. We use the
342-- 'fromAsync' type combinator to effect 'Async' style of composition. We
343-- can also use the 'Async' type annotation for the stream type to achieve
344-- the same effect.
345--
346-- When two streams with multiple elements are combined in this manner, the
347-- streams are traversed in depth first manner just like 'Serial', however it
348-- can execute the next stream concurrently and return the results from it
349-- as they arrive i.e. the results from the next stream may be yielded even
350-- before the results from the first stream. Concurrent execution of the next
351-- stream(s) is performed if the first stream blocks or if it cannot produce
352-- output at the rate that is enough to meet the consumer demand. Multiple
353-- streams can be executed concurrently to meet the demand.
354-- In the example below each element in the stream introduces a constant delay
355-- of 1 second, however, it takes just one second to produce all the results.
356-- The results are not guaranteed to be in any particular order:
357--
358-- >>> p n = threadDelay 1000000 >> return n
359-- >>> stream1 = p 1 |: p 2 |: Stream.nil
360-- >>> stream2 = p 3 |: p 4 |: Stream.nil
361-- >>> Stream.toList $ Stream.fromAsync $ stream1 <> stream2
362-- ...
363--
364-- The constituent streams are also composed in 'Async' manner and the
365-- composition of streams too. We can compose the constituent streams to run
366-- serially, in that case it would take 2 seconds to produce all the results.
367-- The elements in the serial streams would be in serial order in the results:
368--
369-- >>> p n = threadDelay 1000000 >> return n
370-- >>> stream = (Stream.fromSerial stream1) <> (Stream.fromSerial stream2)
371-- >>> Stream.toList $ Stream.fromAsync stream
372-- ...
373--
374-- In the following example we can see that new threads are started when a
375-- computation blocks. Notice that the output from the stream with the
376-- shortest delay is printed first. The whole computation takes @maximum of
377-- (3, 2, 1) = 3@ seconds:
378--
379-- >>> Stream.drain $ Stream.fromAsync $ delay 3 <> delay 2 <> delay 1
380-- ThreadId ...: Delay 1
381-- ThreadId ...: Delay 2
382-- ThreadId ...: Delay 3
383--
384-- When we have a tree of computations composed using this style, the tree is
385-- traversed in DFS style just like the 'Serial' style, the only difference is
386-- that here we can move on to executing the next stream if a stream blocks.
387-- However, we will not start new threads if we have sufficient output to
388-- saturate the consumer. This is why we call it left-biased demand driven or
389-- adaptive concurrency style, the concurrency tends to stay on the left side
390-- of the composition as long as possible. More threads are started based on
391-- the pull rate of the consumer. The following example prints an output every
392-- second as all of the actions are concurrent.
393--
394-- >>> Stream.drain $ Stream.fromAsync $ (delay 1 <> delay 2) <> (delay 3 <> delay 4)
395-- ThreadId ...: Delay 1
396-- ThreadId ...: Delay 2
397-- ThreadId ...: Delay 3
398-- ThreadId ...: Delay 4
399--
400-- All the computations may even run in a single thread when more threads are
401-- not needed. As you can see, in the following example the computations are
402-- run in a single thread one after another, because none of them blocks.
403-- However, if the thread consuming the stream were faster than the producer
404-- then it would have started parallel threads for each computation to keep up
405-- even if none of them blocks:
406--
407-- >>> :{
408-- traced m = Stream.fromEffect (myThreadId >>= print) >> return m
409-- stream = traced (sqrt 9) <> traced (sqrt 16) <> traced (sqrt 25)
410-- main = Stream.drain $ Stream.fromAsync stream
411-- :}
412--
413-- Note that the order of printing in the above examples may change due to
414-- variations in scheduling latencies for concurrent threads.
415--
416-- The polymorphic version of the 'Async' binary operation '<>' is called
417-- 'async'. We can use 'async' to join streams in a left biased
418-- adaptively concurrent manner irrespective of the type, notice that we have
419-- not used the 'fromAsync' combinator in the following example:
420--
421-- >>> Stream.drain $ delay 3 `Stream.async` delay 2 `Stream.async` delay 1
422-- ThreadId ...: Delay 1
423-- ThreadId ...: Delay 2
424-- ThreadId ...: Delay 3
425--
426-- Since the concurrency provided by this operator is demand driven it cannot
427-- be used when the composed computations start timers that are relative to
428-- each other because all computations may not be started at the same time and
429-- therefore timers in all of them may not start at the same time. When
430-- relative timing among all computations is important or when we need to start
431-- all computations at once for any reason 'Parallel' style must be used
432-- instead.
433--
434-- 'Async' style utilizes resources optimally and should be preferred over
435-- 'Parallel' or 'WAsync' unless you really need those. 'Async' should be used
436-- when we know that the computations can run in parallel but we do not care if
437-- they actually run in parallel or not, that decision can be left to the
438-- scheduler based on demand. Also, note that 'async' operator can be used to fold
439-- infinite number of streams in contrast to the 'Parallel' or 'WAsync' styles,
440-- because it does not require us to run all of them at the same time in a fair
441-- manner.
443-- $wasync
444--
445-- The 'Semigroup' operation '<>' of the 'WAsync' type combines two streams in
446-- a concurrent manner using /breadth first traversal/. We use the 'fromWAsync'
447-- type combinator to effect 'WAsync' style of composition. We can also use the
448-- 'WAsync' type annotation for the stream to achieve the same effect.
449--
450-- When streams with multiple elements are combined in this manner, we traverse
451-- all the streams concurrently in a breadth first manner i.e. one action from
452-- each stream is performed and yielded to the resulting stream before we come
453-- back to the first stream again and so on. Even though we execute the actions
454-- in a breadth first order the outputs are consumed on a first come first
455-- serve basis.
456--
457-- In the following example we can see that outputs are produced in the breadth
458-- first traversal order but this is not guaranteed.
459--
460-- >>> stream1 = print 1 |: print 2 |: Stream.nil
461-- >>> stream2 = print 3 |: print 4 |: Stream.nil
462-- >>> Stream.drain $ Stream.fromWAsync $ stream1 <> stream2
463-- 1
464-- 3
465-- 2
466-- 4
467--
468-- The polymorphic version of the binary operation '<>' of the 'WAsync' type is
469-- 'wAsync'. We can use 'wAsync' to join streams using a breadth first
470-- concurrent traversal irrespective of the type, notice that we have not used
471-- the 'fromWAsync' combinator in the following example:
472--
473-- >>> Stream.drain $ delay 3 `Stream.wAsync` delay 2 `Stream.wAsync` delay 1
474-- ThreadId ...: Delay 1
475-- ThreadId ...: Delay 2
476-- ThreadId ...: Delay 3
477--
478-- Since the concurrency provided by this style is demand driven it may not
479-- be used when the composed computations start timers that are relative to
480-- each other because all computations may not be started at the same time and
481-- therefore timers in all of them may not start at the same time. When
482-- relative timing among all computations is important or when we need to start
483-- all computations at once for any reason 'Parallel' style must be used
484-- instead.
485--
487-- $parallel
488--
489-- The 'Semigroup' operation '<>' of the 'Parallel' type combines the two
490-- streams in a fairly concurrent manner with round robin scheduling. We use
491-- the 'fromParallel' type combinator to effect 'Parallel' style of composition.
492-- We can also use the 'Parallel' type annotation for the stream type to
493-- achieve the same effect.
494--
495-- When two streams with multiple elements are combined in this manner, the
496-- monadic actions in both the streams are performed concurrently with a fair
497-- round robin scheduling. The outputs are yielded in the order in which the
498-- actions complete. This is pretty similar to the 'WAsync' type, the
499-- difference is that 'WAsync' is adaptive to the consumer demand and may or
500-- may not execute all actions in parallel depending on the demand, whereas
501-- 'Parallel' runs all the streams in parallel irrespective of the demand.
502--
503-- The polymorphic version of the binary operation '<>' of the 'Parallel' type
504-- is 'parallel'. We can use 'parallel' to join streams in a fairly concurrent
505-- manner irrespective of the type, notice that we have not used the
506-- 'fromParallel' combinator in the following example:
507--
508-- >>> Stream.drain $ delay 3 `Stream.parallel` delay 2 `Stream.wAsync` delay 1
509-- ThreadId ...: Delay 1
510-- ThreadId ...: Delay 2
511-- ThreadId ...: Delay 3
512--
513-- Note that this style of composition cannot be used to combine infinite
514-- number of streams, as it will lead to an infinite sized scheduling queue.
515--
517-- XXX to be removed
518-- $custom
519--
520-- The 'mkAsync' API can be used to create references to asynchronously running
521-- stream computations. We can then use 'uncons' to explore the streams
522-- arbitrarily and then recompose individual elements to create a new stream.
523-- This way we can dynamically decide which stream to explore at any given
524-- time. Take an example of a merge sort of two sorted streams. We need to
525-- keep consuming items from the stream which has the lowest item in the sort
526-- order. This can be achieved using async references to streams. See
527-- "MergeSort.hs" in <https://github.com/composewell/streamly-examples Streamly Examples>.
529-- $monoid
530--
531-- All of the following are equivalent and start ten concurrent tasks each with
532-- a delay from 1 to 10 seconds, resulting in the printing of each number every
533-- second:
534--
535-- >>> :{
536-- main = do
537-- Stream.drain $ Stream.fromAsync $ foldMap delay [1..10]
538-- Stream.drain $ Stream.concatFoldableWith Stream.async (map delay [1..10])
539-- Stream.drain $ Stream.concatMapFoldableWith Stream.async delay [1..10]
540-- Stream.drain $ Stream.concatForFoldableWith Stream.async [1..10] delay
541-- :}
542--
544-- $aheadNesting
545--
546-- The 'Monad' composition of 'Ahead' type behaves just like 'Serial' except
547-- that it can speculatively perform a bounded number of next iterations of a
548-- loop concurrently.
549--
550-- >>> :{
551-- Stream.toList $ Stream.fromAhead $ do
552-- x <- Stream.fromFoldable [3,2,1]
553-- delay x
554-- return x
555-- :}
556-- ThreadId ...: Delay 1
557-- ThreadId ...: Delay 2
558-- ThreadId ...: Delay 3
559-- [3,2,1]
560--
561-- This code finishes in 3 seconds, 'Serial' would take 6 seconds. As we can
562-- see all the three iterations are concurrent and run in different threads,
563-- however, the results are returned in the serial order.
564--
565-- Concurrency is demand driven, when multiple streams are composed using this
566-- style, the iterations are executed in a depth first manner just like
567-- 'Serial' i.e. nested iterations are executed before we proceed to the next
568-- outer iteration. The only difference is that we may execute multiple future
569-- iterations concurrently and keep the results ready.
570--
571-- The 'fromAhead' type combinator can be used to switch to this style of
572-- composition. Alternatively, a type annotation can be used to specify the
573-- type of the stream as 'Ahead'.
574--
576-- $concurrentNesting
577--
578-- The 'Monad' composition of 'Async' type can perform the iterations of a
579-- loop concurrently.
580--
581-- >>> :{
582-- Stream.drain $ Stream.fromAsync $ do
583-- x <- Stream.fromFoldable [3,2,1]
584-- delay x
585-- :}
586-- ThreadId ...: Delay 1
587-- ThreadId ...: Delay 2
588-- ThreadId ...: Delay 3
589--
590-- As we can see the code after the @fromFoldable@ statement is run three
591-- times, once for each value of @x@. All the three iterations are concurrent
592-- and run in different threads. The iteration with least delay finishes first.
593-- When compared to imperative programming, this can be viewed as a @for@ loop
594-- with three concurrent iterations.
595--
596-- Concurrency is demand driven i.e. more concurrent iterations are started
597-- only if the previous iterations are not able to saturate the consumer of the
598-- output stream. This works exactly the same way as the merging of two
599-- streams using 'async' works.
600--
601-- The 'fromAsync' type combinator can be used to switch to this style of
602-- composition. Alternatively, a type annotation can be used to specify the
603-- type of the stream as 'Async'.
604--
605-- When multiple streams are nested using this style, the iterations are
606-- concurrently evaluated in a depth first manner:
607--
608--
609-- >>> :{
610-- Stream.drain $ Stream.fromAsync $ do
611-- x <- Stream.fromFoldable [1,2]
612-- y <- Stream.fromFoldable [3,4]
613-- Stream.fromEffect $ putStrLn $ show (x, y)
614-- :}
615-- (1,3)
616-- ...
617-- ...
618-- ...
619--
620-- Nested iterations are given preference for concurrent evaluation i.e.
621-- (1,4) will be scheduled in preference to (2,3).
623-- $wasyncNesting
624--
625-- Like 'Async', the 'Monad' composition of 'WAsync' runs the iterations of a
626-- loop concurrently. It differs from 'Async' in the nested loop behavior. Like
627-- 'WSerial', the nested loops in this type are traversed and executed in a
628-- breadth first manner rather than the depth first manner of 'Async' style.
629--
630-- >>> :{
631-- Stream.drain $ Stream.fromWAsync $ do
632-- x <- Stream.fromSerial $ Stream.fromFoldable [1,2]
633-- y <- Stream.fromSerial $ Stream.fromFoldable [3,4]
634-- Stream.fromEffect $ putStrLn $ show (x, y)
635-- :}
636-- (1,3)
637-- (1,4)
638-- (2,3)
639-- (2,4)
640--
641-- Note that (2,3) is preferred to (1,4) when evaluating the iterations
642-- concurrently. This works exactly the same way as the merging of two streams
643-- using 'wAsync' works.
644--
645-- The 'fromWAsync' type combinator can be used to switch to this style of
646-- composition. Alternatively, a type annotation can be used to specify the
647-- type of the stream as 'WAsync'.
648--
650-- $parallelNesting
651--
652-- Just like 'Async' or 'WAsync' the 'Monad' composition of 'Parallel' runs the
653-- iterations of a loop concurrently.
654--
655-- >>> :{
656-- Stream.drain $ Stream.fromParallel $ do
657-- x <- Stream.fromFoldable [3,2,1]
658-- delay x
659-- :}
660-- ThreadId ...: Delay 1
661-- ThreadId ...: Delay 2
662-- ThreadId ...: Delay 3
663--
664-- It differs from 'Async' and 'WAsync' in the nested loop behavior. All
665-- iterations of the loop are run fully concurrently irrespective of the
666-- demand. This works exactly the same way as the merging of streams using
667-- 'parallel' works.
668--
669-- The 'fromParallel' type combinator can be used to switch to this style of
670-- composition. Alternatively, a type annotation can be used to specify the
671-- type of the stream as 'Parallel'.
672--
674-- $applicative
675--
676-- 'Async' can run the iterations concurrently, therefore, it takes a total
677-- of 6 seconds which is max (1, 2) + max (3, 4):
678--
679-- >>> (Stream.toList $ Stream.fromAsync $ (,) <$> s1 <*> s2) >>= print
680-- ...
681--
682-- @
683-- ThreadId 34: Delay 1
684-- ThreadId 36: Delay 2
685-- ThreadId 35: Delay 3
686-- ThreadId 36: Delay 3
687-- ThreadId 35: Delay 4
688-- ThreadId 36: Delay 4
689-- [(1,3),(2,3),(1,4),(2,4)]
690-- @
691--
692-- Similarly, 'WAsync' as well can run the iterations concurrently, but with a
693-- different style of scheduling than 'Async' as explained in the Monad
694-- section, therefore, it too takes a total of 6 seconds (2 + 4):
695--
696-- >>> (Stream.toList $ Stream.fromWAsync $ (,) <$> s1 <*> s2) >>= print
697-- ...
698--
699-- @
700-- ThreadId 34: Delay 1
701-- ThreadId 36: Delay 2
702-- ThreadId 35: Delay 3
703-- ThreadId 36: Delay 3
704-- ThreadId 35: Delay 4
705-- ThreadId 36: Delay 4
706-- [(1,3),(2,3),(1,4),(2,4)]
707-- @
709-- $parallelzip
710--
711-- The applicative instance of 'ZipAsync' type zips streams concurrently.
712-- 'fromZipAsync' type combinator can be used to switch to parallel applicative
713-- zip composition:
714--
715-- >>> d n = unCrossStream (CrossStream (delay n) >> return n)
716-- >>> s1 = Stream.fromSerial $ d 2 <> d 4
717-- >>> s2 = Stream.fromSerial $ d 3 <> d 1
718-- >>> (Stream.toList $ Stream.fromZipAsync $ (,) <$> s1 <*> s2) >>= print
719-- ThreadId ...: Delay 2
720-- ThreadId ...: Delay 3
721-- ThreadId ...: Delay 1
722-- ThreadId ...: Delay 4
723-- [(2,3),(4,1)]
724--
725-- The actions within each stream are executed serially, however, the two
726-- streams are run concurrently with respect to each other. Therefore, it takes
727-- 6 seconds to zip the two streams, which is the maximum of the time to
728-- evaluate stream s1 (2 + 4 = 6 seconds) and stream s2 (3 + 1 = 4 seconds).
730-- $concurrent
731--
732-- When writing concurrent programs there are two distinct places where the
733-- programmer can control the concurrency. First, when /composing/ a stream by
734-- merging multiple streams we can choose an appropriate sum style operators to
735-- combine them concurrently or serially. Second, when /processing/ a stream in
736-- a monadic composition we can choose one of the monad composition types to
737-- choose the desired type of concurrency.
738--
739-- In the following example the squares of @x@ and @y@ are computed
740-- concurrently using the 'async' operation and the square roots of their
741-- sum are computed serially because of the 'fromSerial' combinator. We can
742-- choose different combinators for the monadic processing and the stream
743-- generation, to control the concurrency. We can also use the 'fromAsync'
744-- combinator instead of explicitly folding with 'async'.
745--
746-- >>> import Data.List (sum)
747-- >>> :{
748-- main = do
749-- z <- Stream.toList
750-- $ Stream.fromSerial -- Serial monadic processing (sqrt below)
751-- $ unCrossStream $ do
752-- -- Concurrent @"for"@ loop
753-- x2 <- CrossStream (Stream.concatForFoldableWith Stream.async [1..100] $
754-- \x -> Stream.fromPure $ x * x) -- body of the loop
755-- y2 <- CrossStream (Stream.concatForFoldableWith Stream.async [1..100] $
756-- \y -> Stream.fromPure $ y * y)
757-- return $ sqrt (x2 + y2)
758-- print $ sum z
759-- :}
760--
761-- We can see how this directly maps to the imperative style
762-- <https://en.wikipedia.org/wiki/OpenMP OpenMP> model, we use combinators
763-- and operators instead of the ugly pragmas.
764--
765-- For more concurrent programming examples see,
766-- <https://github.com/composewell/streamly-examples>.
768-- $furtherReading
769--
770-- * Read the reactive programming tutorial
771-- * See the examples in <https://github.com/composewell/streamly-examples streamly-examples> repo.