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