on fa3d53d help sign in
streamly/src/Streamly/Tutorial.hs
849 lines 26 kB history raw
1{-# OPTIONS_GHC -fno-warn-unused-imports #-}
2-- |
3-- Module : Streamly.Tutorial
4-- Copyright : (c) 2017 Harendra Kumar
5--
6-- License : BSD3
7-- Maintainer : harendra.kumar@gmail.com
8--
9-- Streamly, short for stream concurrently, combines the essence of streaming
10-- and concurrency in functional programming. You can write concurrent as well
11-- as non-concurrent applications with streamly. Streaming enables writing
12-- modular, composable and scalable applications with ease and concurrency
13-- allows you to make them scale and perform well.
14-- Streamly enables writing concurrent applications without being aware of
15-- threads or synchronization. No explicit thread control is needed, where
16-- applicable the concurrency rate is automatically controlled based on the
17-- demand by the consumer. However, combinators are provided to fine tune the
18-- concurrency control.
19-- Streaming and concurrency together enable expressing reactive applications
20-- conveniently. See 'Streamly.Examples' for a simple SDL based FRP example.
21--
22-- In this tutorial we will go over the basic concepts and how to use the
23-- library. For examples and other ways to use the library see
24-- the module @Streamly.Examples@ as well.
26module Streamly.Tutorial
27 (
28 -- * Streams
29 -- $streams
31 -- ** Generating Streams
32 -- $generating
34 -- ** Eliminating Streams
35 -- $eliminating
37 -- * Folding Streams
38 -- $folding
40 -- * Semigroup Style Composition
41 -- $semigroup
43 -- ** Serial composition ('<>')
44 -- $serial
46 -- ** Async composition ('<|')
47 -- $parallel
49 -- ** Interleaved composition ('<=>')
50 -- $interleaved
52 -- ** Parallel composition ('<|>')
53 -- $fairParallel
55 -- ** Custom composition
56 -- $custom
58 -- * Monoid Style composition
59 -- $monoid
61 -- * Transforming Streams
62 -- $transforming
64 -- ** Monad
65 -- $monad
67 -- *** Serial Iterations ('StreamT')
68 -- $regularSerial
70 -- *** Concurrent Iterations ('AsyncT')
71 -- $concurrentNesting
73 -- *** Interleaved Iterations ('InterleavedT')
74 -- $interleavedNesting
76 -- *** Fairly Concurrent Iterations ('ParallelT')
77 -- $fairlyConcurrentNesting
79 -- *** Exercise
80 -- $monadExercise
82 -- ** Applicative
83 -- $applicative
85 -- ** Functor
86 -- $functor
88 -- * Summary of Compositions
89 -- $compositionSummary
91 -- * Zipping Streams
92 -- $zipping
94 -- ** Serial Zipping
95 -- $serialzip
97 -- ** Parallel Zipping
98 -- $parallelzip
100 -- * Concurrent Programming Examples
101 -- $concurrent
103 -- * Reactive Programming
104 -- $reactive
106 -- * State Machine Model
107 -- $statemachine
109 -- * Performance
110 -- $performance
112 -- * Interworking with Streaming Libraries
113 -- $interwork
115 -- * Comparison with Existing Packages
116 -- $comparison
117 )
118where
120import Streamly
121import Streamly.Prelude
122-- import Streamly.Examples
123import Data.Semigroup
124import Control.Applicative
125import Control.Monad
126import Control.Monad.IO.Class (MonadIO(..))
127import Control.Monad.Trans.Class (MonadTrans (lift))
129-- $streams
130--
131-- Streamly provides many different stream types depending on the desired
132-- composition style. The simplest type is 'StreamT'. 'StreamT' is a monad
133-- transformer, the type @StreamT m a@ represents a stream of values of type
134-- 'a' in some underlying monad 'm'. For example, @StreamT IO Int@ is a stream
135-- of 'Int' in 'IO' monad.
137-- $generating
138--
139-- Pure values can be placed into the stream type using 'return' or 'pure'.
140-- Effects in the IO monad can be lifted to the stream type using the 'liftIO'
141-- combinator. In a transformer stack we can lift actions from the lower monad
142-- using the 'lift' combinator. Some examples of streams with a single element:
143--
144-- @
145-- return 1 :: StreamT IO Int
146-- @
147-- @
148-- liftIO $ putStrLn "Hello world!" :: StreamT IO ()
149-- @
150--
151-- We can combine streams using '<>' to create streams of many elements:
152--
153-- @
154-- return 1 <> return 2 <> return 3 :: StreamT IO Int
155-- @
157-- $eliminating
158--
159-- 'runStreamT' runs a composed 'StreamT' computation, lowering the type into
160-- the underlying monad and discarding the result stream:
161--
162-- @
163-- import Streamly
164--
165-- main = runStreamT $ liftIO $ putStrLn "Hello world!"
166-- @
167--
168-- 'toList' runs a stream computation and collects the result stream in a list
169-- in the underlying monad. 'toList' is a polymorphic function that works on
170-- multiple stream types belonging to the class 'Streaming'. Therefore, before
171-- you run a stream you need to tell how you want to interpret the stream by
172-- using one of the stream type combinators ('serially', 'asyncly', 'parallely'
173-- etc.). The combinator 'serially' is equivalent to annotating the type as @::
174-- StreamT@.
175--
176-- @
177-- import Streamly
178--
179-- main = do
180-- xs \<- toList $ serially $ return 1 <> return 2
181-- print xs
182-- @
183--
184-- For other ways to eliminate a stream see the module @Streamly.Prelude@.
186-- $semigroup
187-- Streams of the same type can be combined into a composite stream in many
188-- different ways using one of the semigroup style binary composition operators
189-- i.e. '<>', '<=>', '<|', '<|>', 'mplus'. These operators work on all stream
190-- types ('StreamT', 'AsyncT' etc.) uniformly.
191--
192-- To illustrate the concurrent aspects, we will use the following @delay@
193-- function to introduce a delay specified in seconds.
194--
195-- @
196-- import Streamly
197-- import Control.Concurrent
198--
199-- delay n = liftIO $ do
200-- threadDelay (n * 1000000)
201-- tid \<- myThreadId
202-- putStrLn (show tid ++ ": Delay " ++ show n)
203-- @
205-- $serial
206--
207-- We have already seen, the '<>' operator. It composes two streams in series
208-- i.e. the first stream is completely exhausted and then the second stream is
209-- processed. The following example prints the sequence 3, 2, 1 and takes a
210-- total of 6 seconds because everything is serial:
211--
212-- @
213-- main = runStreamT $ delay 3 <> delay 2 <> delay 1
214-- @
215-- @
216-- ThreadId 36: Delay 3
217-- ThreadId 36: Delay 2
218-- ThreadId 36: Delay 1
219-- @
221-- $interleaved
222-- The '<=>' operator is serial like '<>' but it interleaves the two streams
223-- i.e. it yields one element from the first stream and then one element from
224-- the second stream, and so on. The following example prints the sequence 1,
225-- 3, 2, 4 and takes a total of 10 seconds because everything is serial:
226--
227-- @
228-- main = runStreamT $ (delay 1 <> delay 2) \<=> (delay 3 <> delay 4)
229-- @
230-- @
231-- ThreadId 36: Delay 1
232-- ThreadId 36: Delay 3
233-- ThreadId 36: Delay 2
234-- ThreadId 36: Delay 4
235-- @
236--
237-- Note that this operator cannot be used to fold infinite containers since it
238-- requires preserving the state until a stream is finished.
240-- $parallel
241--
242-- The '<|' operator can run both computations concurrently, /when needed/.
243-- In the following example since the first computation blocks we start the
244-- next one in a separate thread and so on:
245--
246-- @
247-- main = runStreamT $ delay 3 <| delay 2 <| delay 1
248-- @
249-- @
250-- ThreadId 42: Delay 1
251-- ThreadId 41: Delay 2
252-- ThreadId 40: Delay 3
253-- @
254--
255-- This is the concurrent version of the '<>' operator. The computations are
256-- triggered in the same order as '<>' except that they are concurrent. When
257-- we have a tree of computations composed using this operator, the tree is
258-- traversed in DFS style just like '<>'.
259--
260-- @
261-- main = runStreamT $ (p 1 <| p 2) <| (p 3 <| p 4)
262-- where p = liftIO . print
263-- @
264-- @
265-- 1
266-- 2
267-- 3
268-- 4
269-- @
270--
271-- Concurrency provided by this operator is demand driven. The second
272-- computation is run concurrently with the first only if the first computation
273-- is not producing enough output to keep the stream consumer busy otherwise
274-- the second computation is run serially after the previous one. The number of
275-- concurrent threads is adapted dynamically based on the pull rate of the
276-- consumer of the stream.
277-- As you can see, in the following example the computations are run in a
278-- single thread one after another, because none of them blocks. However, if
279-- the thread consuming the stream were faster than the producer then it would
280-- have started parallel threads for each computation to keep up even if none
281-- of them blocks:
282--
283-- @
284-- main = runStreamT $ traced (sqrt 9) <| traced (sqrt 16) <| traced (sqrt 25)
285-- @
286-- @
287-- ThreadId 40
288-- ThreadId 40
289-- ThreadId 40
290-- @
291--
292-- Since the concurrency provided by this operator is demand driven it cannot
293-- be used when the composed computations have timers that are relative to each
294-- other because all computations may not be started at the same time and
295-- therefore timers in all of them may not start at the same time. When
296-- relative timing among all computations is important or when we need to start
297-- all computations at once for some reason '<|>' must be used instead.
298-- However, '<|' is useful in situations when we want to optimally utilize the
299-- resources and we know that the computations can run in parallel but we do
300-- not care if they actually run in parallel or not, that decision is left to
301-- the scheduler. Also, note that this operator can be used to fold infinite
302-- containers in contrast to '<|>', because it does not require us to run all
303-- of them at the same time.
304--
305-- The left bias (or the DFS style) of the operator '<|' is suggested by its
306-- shape. You can also think of this as an unbalanced version of the fairly
307-- parallel operator '<|>'.
309-- $fairParallel
310--
311-- The 'Alternative' composition operator '<|>', like '<|', runs the composed
312-- computations concurrently. However, unlike '<|' it runs all of the
313-- computations in fairly parallel manner using a round robin scheduling
314-- mechanism. This can be considered as the concurrent version of the fairly
315-- interleaved serial operation '<=>'. Note that this cannot be used on
316-- infinite containers, as it will lead to an infinite sized scheduling queue.
317--
318-- The following example sends a query to three search engines in parallel and
319-- prints the name of the search engine as a response arrives:
320--
321-- @
322-- import Streamly
323-- import Network.HTTP.Simple
324--
325-- main = runStreamT $ google \<|> bing \<|> duckduckgo
326-- where
327-- google = get "https://www.google.com/search?q=haskell"
328-- bing = get "https://www.bing.com/search?q=haskell"
329-- duckduckgo = get "https://www.duckduckgo.com/?q=haskell"
330-- get s = liftIO (httpNoBody (parseRequest_ s) >> putStrLn (show s))
331-- @
333-- $custom
334--
335-- The 'async' API can be used to create references to asynchronously running
336-- stream computations. We can then use 'uncons' to explore the streams
337-- arbitrarily and then recompose individual elements to create a new stream.
338-- This way we can dynamically decide which stream to explore at any given
339-- time. Take an example of a merge sort of two sorted streams. We need to
340-- keep consuming items from the stream which has the lowest item in the sort
341-- order. This can be achieved using async references to streams. See
342-- 'Streamly.Examples.MergeSortedStreams'.
344-- $monoid
345--
346-- Each of the semigroup compositions described has an identity that can be
347-- used to fold a possibly empty container. An empty stream is represented by
348-- 'nil' which can be represented in various standard forms as 'mempty',
349-- 'empty' or 'mzero'.
350-- Some fold utilities are also provided by the library for convenience:
351--
352-- * 'foldWith' folds a 'Foldable' container of stream computations using the
353-- given composition operator.
354-- * 'foldMapWith' folds like foldWith but also maps a function before folding.
355-- * 'forEachWith' is like foldMapwith but the container argument comes before
356-- the function argument.
357-- * The 'each' primitive from @Streamly.Prelude@ folds a 'Foldable' container
358-- using the '<>' operator:
359--
360-- All of the following are equivalent:
361--
362-- @
363-- import Streamly
364--
365-- main = do
366-- toList . serially $ foldWith (<>) (map return [1..10]) >>= print
367-- toList . serially $ foldMapWith (<>) return [1..10] >>= print
368-- toList . serially $ forEachWith (<>) [1..10] return >>= print
369-- toList . serially $ each [1..10] >>= print
370-- @
372-- $transforming
373--
374-- The previous section discussed ways to merge the elements of two streams
375-- without doing any transformation on them. In this section we will explore
376-- how to transform streams using 'Functor', 'Applicative' or 'Monad' style
377-- compositions. The applicative and monad composition of all 'Streaming' types
378-- behave exactly the same way as a list transformer. For simplicity of
379-- illustration we are using streams of pure values in the following examples.
380-- However, the real application of streams arises when these streams are
381-- generated using monadic actions.
383-- $monad
384--
385-- In functional programmer's parlance the 'Monad' instance of 'Streaming'
386-- types implement non-determinism, exploring all possible combination of
387-- choices from both the streams. From an imperative programmer's point of view
388-- it behaves like nested loops i.e. for each element in the first stream and
389-- for each element in the second stream apply the body of the loop. If you are
390-- familiar with list transformer this behavior is exactly the same as that of
391-- a list transformer.
392--
393-- Just like we saw in sum style compositions earlier, monadic composition also
394-- has multiple variants each of which exactly corresponds to one of the sum
395-- style composition variant.
397-- $regularSerial
398--
399-- When we interpret the monadic composition as 'StreamT' we get a standard
400-- list transformer like serial composition.
401--
402-- @
403-- main = runStreamT $ do
404-- x <- each [3,2,1]
405-- delay x
406-- @
407-- @
408-- ThreadId 30: Delay 3
409-- ThreadId 30: Delay 2
410-- ThreadId 30: Delay 1
411-- @
412--
413-- As you can see the code after the @each@ statement is run three times, once
414-- for each value of @x@. All the three iterations are serial and run in the
415-- same thread one after another. When compared to imperative programming, this
416-- can also be viewed as a @for@ loop with three iterations.
417--
418-- When multiple streams are composed using this style they nest in a DFS
419-- manner i.e. nested iterations of an iteration are executed before we proceed
420-- to the next iteration at higher level. This behaves just like nested @for@
421-- loops in imperative programming.
422--
423-- @
424-- main = runStreamT $ do
425-- x <- each [1,2]
426-- y <- each [3,4]
427-- liftIO $ putStrLn $ show (x, y)
428-- @
429-- @
430-- (1,3)
431-- (1,4)
432-- (2,3)
433-- (2,4)
434-- @
435--
436-- You will also notice that this is the monadic equivalent of the sum style
437-- composition using '<>'.
439-- $concurrentNesting
440--
441-- When we interpret the monadic composition as 'AsyncT' we get a /concurrent/
442-- list-transformer like composition. Multiple monadic continuations (or loop
443-- iterations) may be started concurrently. Concurrency is demand driven
444-- i.e. more concurrent iterations are started only if the previous iterations
445-- are not able to produce enough output for the consumer of the output stream.
446-- This is the concurrent version of 'StreamT'.
447--
448-- @
449-- main = runAsyncT $ do
450-- x <- each [3,2,1]
451-- delay x
452-- @
453-- @
454-- ThreadId 40: Delay 1
455-- ThreadId 39: Delay 2
456-- ThreadId 38: Delay 3
457-- @
458--
459-- As you can see the code after the @each@ statement is run three times, once
460-- for each value of @x@. All the three iterations are concurrent and run in
461-- different threads. The iteration with least delay finishes first. When
462-- compared to imperative programming, this can be viewed as a @for@ loop
463-- with three concurrent iterations.
464--
465-- Concurrency is demand driven just as in the case of '<|'. When multiple
466-- streams are composed using this style the iterations are triggered in a DFS
467-- manner just like 'StreamT' i.e. nested iterations are executed before we
468-- proceed to the next iteration at higher level. However, unlike 'StreamT'
469-- more than one iterations may be started concurrently, and based on the
470-- demand from the consumer.
471--
472-- @
473-- main = runAsyncT $ do
474-- x <- each [1,2]
475-- y <- each [3,4]
476-- liftIO $ putStrLn $ show (x, y)
477-- @
478-- @
479-- (1,3)
480-- (1,4)
481-- (2,3)
482-- (2,4)
483-- @
484--
485-- You will notice that this is the monadic equivalent of the '<|' style
486-- sum composition. The same caveats apply to this as the '<|' operation.
488-- $interleavedNesting
489--
490-- When we interpret the monadic composition as 'InterleavedT' we get a serial
491-- but fairly interleaved list-transformer like composition. The monadic
492-- continuations or iterations of the outer loop are fairly interleaved with
493-- the continuations or iterations of the inner loop.
494--
495-- @
496-- main = runInterleavedT $ do
497-- x <- each [1,2]
498-- y <- each [3,4]
499-- liftIO $ putStrLn $ show (x, y)
500-- @
501-- @
502-- (1,3)
503-- (2,3)
504-- (1,4)
505-- (2,4)
506-- @
507--
508-- You will notice that this is the monadic equivalent of the '<=>' style
509-- sum composition. The same caveats apply to this as the '<=>' operation.
511-- $fairlyConcurrentNesting
512--
513-- When we interpret the monadic composition as 'ParallelT' we get a
514-- /concurrent/ list-transformer like composition just like 'AsyncT'. The
515-- difference is that this is fully parallel with all iterations starting
516-- concurrently instead of the demand driven concurrency of 'AsyncT'.
517--
518-- @
519-- main = runParallelT $ do
520-- x <- each [3,2,1]
521-- delay x
522-- @
523-- @
524-- ThreadId 40: Delay 1
525-- ThreadId 39: Delay 2
526-- ThreadId 38: Delay 3
527-- @
528--
529-- You will notice that this is the monadic equivalent of the '<|>' style
530-- sum composition. The same caveats apply to this as the '<|>' operation.
532-- $monadExercise
533--
534-- The streamly code is usually written in a way that is agnostic of the
535-- specific monadic composition type. We use a polymorphic type with a
536-- 'Streaming' type class constraint. When running the stream we can choose the
537-- specific mode of composition. For example look at the following code.
538--
539-- @
540-- import Streamly
541--
542-- composed :: Streaming t => t m a
543-- composed = do
544-- sz <- sizes
545-- cl <- colors
546-- sh <- shapes
547-- liftIO $ putStrLn $ show (sz, cl, sh)
548--
549-- where
550--
551-- sizes = each [1, 2, 3]
552-- colors = each ["red", "green", "blue"]
553-- shapes = each ["triangle", "square", "circle"]
554-- @
555--
556-- Now we can interpret this in whatever way we want:
557--
558-- @
559-- main = runStreamT composed
560-- main = runAsyncT composed
561-- main = runInterleavedT composed
562-- main = runParallelT composed
563-- @
564--
565-- Equivalently, we can also write it using the type adapter combinators, like
566-- this:
567--
568-- @
569-- main = runStreaming $ serially $ composed
570-- main = runStreaming $ asyncly $ composed
571-- main = runStreaming $ interleaving $ composed
572-- main = runStreaming $ parallely $ composed
573-- @
574--
575-- As an exercise try to figure out the output of this code for each mode of
576-- composition.
578-- $functor
579--
580-- The simplest transformation on a stream is mapping a function on
581-- all elements of the stream using 'fmap'.
582--
583-- @
584-- import Streamly
585--
586-- main = (toList $ serially $ fmap show $ each [1..10]) >>= print
587-- @
589-- $applicative
590--
591-- Applicative is a special case of monad and behaves just like the monadic
592-- composition. The following example runs all iterations serially and takes a
593-- total 17 seconds (1 + 3 + 4 + 2 + 3 + 4):
594--
595-- @
596-- import Streamly
597-- import Streamly.Prelude
598-- import Control.Concurrent
599--
600-- s1 = d 1 <> d 2
601-- s2 = d 3 <> d 4
602-- d n = delay n >> return n
603--
604-- main = (toList . serially $ (,) \<$> s1 \<*> s2) >>= print
605-- @
606-- @
607-- ThreadId 36: Delay 1
608-- ThreadId 36: Delay 3
609-- ThreadId 36: Delay 4
610-- ThreadId 36: Delay 2
611-- ThreadId 36: Delay 3
612-- ThreadId 36: Delay 4
613-- [(1,3),(1,4),(2,3),(2,4)]
614-- @
615--
616-- Similalrly interleaving runs the iterations in an interleaved order but
617-- since it is serial it takes a total of 17 seconds:
618--
619-- @
620-- main = (toList . interleaving $ (,) \<$> s1 \<*> s2) >>= print
621-- @
622-- @
623-- ThreadId 36: Delay 1
624-- ThreadId 36: Delay 3
625-- ThreadId 36: Delay 2
626-- ThreadId 36: Delay 3
627-- ThreadId 36: Delay 4
628-- ThreadId 36: Delay 4
629-- [(1,3),(2,3),(1,4),(2,4)]
630-- @
631--
632-- 'AsyncT' can run the iterations concurrently and therefore takes a total
633-- of 10 seconds (1 + 2 + 3 + 4):
634--
635-- @
636-- main = (toList . asyncly $ (,) \<$> s1 \<*> s2) >>= print
637-- @
638-- @
639-- ThreadId 34: Delay 1
640-- ThreadId 36: Delay 2
641-- ThreadId 35: Delay 3
642-- ThreadId 36: Delay 3
643-- ThreadId 35: Delay 4
644-- ThreadId 36: Delay 4
645-- [(1,3),(2,3),(1,4),(2,4)]
646-- @
647--
648-- Similalrly 'ParallelT' as well can run the iterations concurrently and
649-- therefore takes a total of 10 seconds (1 + 2 + 3 + 4):
650--
651-- @
652-- main = (toList . parallely $ (,) \<$> s1 \<*> s2) >>= print
653-- @
654-- @
655-- ThreadId 34: Delay 1
656-- ThreadId 36: Delay 2
657-- ThreadId 35: Delay 3
658-- ThreadId 36: Delay 3
659-- ThreadId 35: Delay 4
660-- ThreadId 36: Delay 4
661-- [(1,3),(2,3),(1,4),(2,4)]
662-- @
664-- $folding
665-- Folds
667-- $compositionSummary
668--
669-- The following table summarizes the types for monadic compositions and the
670-- operators for sum style compositions. This table captures the essence of
671-- streamly.
672--
673-- @
674-- +-----+--------------+------------+
675-- | | Serial | Concurrent |
676-- +=====+==============+============+
677-- | DFS | 'StreamT' | 'AsyncT' |
678-- | +--------------+------------+
679-- | | '<>' | '<|' |
680-- +-----+--------------+------------+
681-- | BFS | 'InterleavedT' | 'ParallelT' |
682-- | +--------------+------------+
683-- | | '<=>' | '<|>' |
684-- +-----+--------------+------------+
685-- @
687-- $zipping
688--
689-- Zipping is a special transformation where the corresponding elements of two
690-- streams are combined together using a zip function producing a new
691-- stream of outputs. Two different types are provided for serial and
692-- concurrent zipping. These types provide an applicative instance that zips
693-- the argument streams.
695-- $serialzip
696--
697-- 'StreamZip' zips streams serially:
698--
699-- @
700-- import Streamly
701-- import Streamly.Prelude
702-- import Control.Concurrent
703--
704-- d n = delay n >> return n
705-- s1 = adapt . serially $ d 1 <> d 2
706-- s2 = adapt . serially $ d 3 <> d 4
707--
708-- main = (toList . zipping $ (,) \<$> s1 \<*> s2) >>= print
709-- @
710--
711-- This takes total 10 seconds to zip, which is (1 + 2 + 3 + 4) since
712-- everything runs serially:
713--
714-- @
715-- ThreadId 29: Delay 1
716-- ThreadId 29: Delay 3
717-- ThreadId 29: Delay 2
718-- ThreadId 29: Delay 4
719-- [(1,3),(2,4)]
720-- @
722-- $parallelzip
723--
724-- 'AsyncZip' zips streams concurrently:
725--
726-- @
727-- import Streamly
728-- import Streamly.Prelude
729-- import Control.Concurrent
730-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
731--
732-- d n = delay n >> return n
733-- s1 = adapt . serially $ d 1 <> d 2
734-- s2 = adapt . serially $ d 3 <> d 4
735--
736-- main = do
737-- liftIO $ hSetBuffering stdout LineBuffering
738-- (toList . zippingAsync $ (,) \<$> s1 \<*> s2) >>= print
739-- @
740--
741-- This takes 7 seconds to zip, which is max (1,3) + max (2,4) because 1 and 3
742-- are produced concurrently, and 2 and 4 are produced concurrently:
743--
744-- @
745-- ThreadId 32: Delay 1
746-- ThreadId 32: Delay 2
747-- ThreadId 33: Delay 3
748-- ThreadId 33: Delay 4
749-- [(1,3),(2,4)]
750-- @
752-- $concurrent
753--
754-- There are two ways to achieve concurrency. We can generate individual
755-- elements of a stream concurrently by folding with parallel composition
756-- operators i.e. '<|' or '<|>'. 'forEachWith' can be useful in such cases.
757--
758-- In the following example, we square each number concurrently but then
759-- sum and print them serially:
760--
761-- @
762-- import Streamly
763-- import Streamly.Prelude (toList)
764-- import Data.List (sum)
765--
766-- main = do
767-- squares \<- toList $ serially $ foldMapWith (<|) (\x -\> return $ x * x) [1..100]
768-- print $ sum squares
769-- @
770--
771-- The following example not just computes the squares concurrently but also
772-- computes the square root of their sums concurrently by using the parallel
773-- monadic bind.
774--
775-- @
776-- import Streamly
777-- import Streamly.Prelude (toList)
778-- import Data.List (sum)
779--
780-- main = do
781-- z \<- toList $ asyncly $ do
782-- xsq \<- foldMapWith (\<|) (\\x -> return $ x * x) [1..100]
783-- ysq \<- foldMapWith (\<|) (\\x -> return $ x * x) [1..100]
784-- return $ sqrt (xsq + ysq)
785-- print $ sum z
786-- @
788-- $reactive
789--
790-- Let us see a reactive programming example:
791--
792-- @
793-- {-\# LANGUAGE FlexibleContexts #-}
794--
795-- import Streamly
796-- import Control.Concurrent (threadDelay)
797-- import Control.Monad (when)
798-- import Control.Monad.State
799-- import Data.Semigroup (cycle1)
800--
801-- data Event = Harm Int | Heal Int | Quit deriving (Show)
802--
803-- userAction :: MonadIO m => StreamT m Event
804-- userAction = cycle1 $ liftIO askUser
805-- where
806-- askUser = do
807-- command <- getLine
808-- case command of
809-- "potion" -> return (Heal 10)
810-- "quit" -> return Quit
811-- _ -> putStrLn "What?" >> askUser
812--
813-- acidRain :: MonadIO m => StreamT m Event
814-- acidRain = cycle1 $ liftIO (threadDelay 1000000) >> return (Harm 1)
815--
816-- game :: (MonadAsync m, MonadState Int m) => StreamT m ()
817-- game = do
818-- event \<- userAction \<|> acidRain
819-- case event of
820-- Harm n -> modify $ \\h -> h - n
821-- Heal n -> modify $ \\h -> h + n
822-- Quit -> fail "quit"
823--
824-- h <- get
825-- when (h <= 0) $ fail "You die!"
826-- liftIO $ putStrLn $ "Health = " ++ show h
827--
828-- main = do
829-- putStrLn "Your health is deteriorating due to acid rain,\\
830-- \\ type \\"potion\\" or \\"quit\\""
831-- _ <- runStateT (runStreamT game) 60
832-- return ()
833-- @
835-- $statemachine
836-- State machine stuff
838-- $comparison
839--
840-- Even though streamly covers all that is provided by the 'async' package or
841-- most of what is provided by the 'streaming', 'pipes' or 'conduit' packages,
842-- I would not say that it renders those useless. Streamly is like monad if
843-- 'async' is applicative and monads and applicatives both have their use
844-- cases. It can completely repalce 'async', the ZipAsync type is equivalent to
845-- the functionality provided by 'async'.
846--
847-- pipes and conduit are like Arrows and streamly is like monad to them. You
848-- would use pipes and conduit when you do not need the product style
849-- composition and the implicit concurrency.