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