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