on 5708ef6 help sign in
streamly/src/Streamly/Tutorial.hs
1729 lines 64 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 is a general computing framework based on streaming IO. The IO
10-- monad and pure lists are a special case of streamly. On one hand, streamly
11-- extends the lists of pure values to lists of monadic actions, on the other
12-- hand it extends the IO monad with concurrent non-determinism. In simple
13-- imperative terms we can say that streamly extends the IO monad with @for@
14-- loops and nested @for@ loops with concurrency support. You can understand
15-- this analogy better once you can go through this tutorial.
16--
17-- Streaming in general enables writing modular, composable and scalable
18-- applications with ease, and concurrency allows you to make them scale and
19-- perform well. Streamly enables writing scalable concurrent applications
20-- without being aware of threads or synchronization. No explicit thread
21-- control is needed, where applicable the concurrency rate is automatically
22-- controlled based on the demand by the consumer. However, combinators can be
23-- used to fine tune the concurrency control.
24--
25-- Streaming and concurrency together enable expressing reactive applications
26-- conveniently. See the @CirclingSquare@ example in the examples directory for
27-- a simple SDL based FRP example. To summarize, streamly provides a unified
28-- computing framework for streaming, non-determinism and functional reactive
29-- programming in an elegant and simple API that is a natural extension of pure
30-- lists to monadic streams.
31--
32-- In this tutorial we will go over the basic concepts and how to
33-- use the library. See the last section for further reading resources.
35module Streamly.Tutorial
36 (
37 -- * Streams
38 -- $streams
40 -- * Concurrent Streams
41 -- $concurrentStreams
43 -- * Combining Streams
44 -- $flavors
46 -- * Imports and Supporting Code
47 -- $imports
49 -- * Generating Streams
50 -- $generating
52 -- * Generating Streams Concurrently
53 -- $generatingConcurrently
55 -- * Eliminating Streams
56 -- $eliminating
58 -- * Concurrent Pipeline Stages
59 -- $concurrentApplication
61 -- * Transforming Streams
62 -- $transformation
64 -- * Mapping Concurrently
65 -- $concurrentTransformation
67 -- * Merging Streams
69 -- ** Semigroup Style
70 -- $semigroup
72 -- *** Deep Serial Composition ('Serial')
73 -- $serial
75 -- *** Wide Serial Composition ('WSerial')
76 -- $interleaved
78 -- *** Deep Ahead Composition ('Ahead')
79 -- $ahead
81 -- *** Deep Asynchronous Composition ('Async')
82 -- $async
84 -- *** Wide Asynchronous Composition ('WAsync')
85 -- $wasync
87 -- *** Parallel Asynchronous Composition ('Parallel')
88 -- $parallel
90 -- XXX we should deprecate and remove the mkAsync API
91 -- Custom composition
92 -- custom
94 -- ** Monoid Style
95 -- $monoid
97 -- * Nesting Streams
98 -- $nesting
100 -- ** Monad
101 -- $monad
103 -- *** Deep Serial Nesting ('Serial')
104 -- $regularSerial
106 -- *** Wide Serial Nesting ('WSerial')
107 -- $interleavedNesting
109 -- *** Deep Ahead Nesting ('Ahead')
110 -- $aheadNesting
112 -- *** Deep Asynchronous Nesting ('Async')
113 -- $concurrentNesting
115 -- *** Wide Asynchronous Nesting ('WAsync')
116 -- $wasyncNesting
118 -- *** Parallel Asynchronous Nesting ('Parallel')
119 -- $parallelNesting
121 -- *** Exercise
122 -- $monadExercise
124 -- ** Applicative
125 -- $applicative
127 -- ** Functor
128 -- $functor
130 -- * Zipping Streams
131 -- $zipping
133 -- ** Serial Zipping
134 -- $serialzip
136 -- ** Parallel Zipping
137 -- $parallelzip
139 -- * Monad transformers
140 -- $monadtransformers
142 -- * Concurrent Programming
143 -- $concurrent
145 -- * Reactive Programming
146 -- $reactive
148 -- * Writing Concurrent Programs
149 -- $programs
151 -- * Performance
152 -- $performance
154 -- * Interoperation with Streaming Libraries
155 -- $interop
157 -- * Comparison with Existing Packages
158 -- $comparison
160 -- * Where to go next?
161 -- $furtherReading
162 )
163where
165import Streamly
166import Streamly.Prelude
167import Data.Semigroup
168import Control.Applicative
169import Control.Monad
170import Control.Monad.IO.Class (MonadIO(..))
171import Control.Monad.Trans.Class (MonadTrans (lift))
173-- $streams
174--
175-- The way a list represents a sequence of pure values, a stream represents a
176-- sequence of monadic actions. The monadic stream API offered by Streamly is
177-- very close to the Haskell "Prelude" pure lists' API, it can be considered as a
178-- natural extension of lists to monadic actions. Streamly streams provide
179-- concurrent composition and merging of streams. It can be considered as a
180-- concurrent list transformer. In contrast to the "Prelude" lists, merging or
181-- appending streams of arbitrary length is scalable and inexpensive.
182--
183-- The basic stream type is 'Serial', it represents a sequence of IO actions,
184-- and is a 'Monad'. The 'Serial' monad is almost a drop in replacement for
185-- the 'IO' monad, IO monad is a special case of the 'Serial' monad; IO monad
186-- represents a single IO action whereas the 'Serial' monad represents a series
187-- of IO actions. The only change you need to make to go from 'IO' to 'Serial'
188-- is to use 'runStream' to run the monad and to prefix the IO actions with
189-- either 'yieldM' or 'liftIO'. If you use liftIO you can switch from 'Serial'
190-- to IO monad by simply removing the 'runStream' function; no other changes
191-- are needed unless you have used some stream specific composition or
192-- combinators.
193--
194-- Similarly, the 'Serial' type is almost a drop in replacement for pure lists,
195-- pure lists are a special case of monadic streams. If you use 'nil' in place
196-- of '[]' and '|:' in place ':' you can replace a list with a 'Serial' stream.
197-- The only difference is that the elements must be monadic type and to operate
198-- on the streams we must use the corresponding functions from
199-- "Streamly.Prelude" instead of using the base "Prelude".
201-- $concurrentStreams
202--
203-- Many stream operations can be done concurrently:
204--
205-- * Streams can be generated concurrently.
206--
207-- * Streams can be merged concurrently.
208--
209-- * Multiple stages in a streaming pipeline can run concurrently.
210--
211-- * Streams can be mapped and zipped concurrently.
212--
213-- * In monadic composition they combine like a list transformer,
214-- providing concurrent non-determinism.
215--
216-- There are three basic concurrent stream styles, 'Ahead', 'Async', and
217-- 'Parallel'. The 'Ahead' style streams are similar to 'Serial' except that
218-- they can speculatively execute multiple stream actions concurrently in
219-- advance. 'Ahead' would return exactly the same stream as 'Serial' except
220-- that it may execute the actions concurrently. The 'Async' style streams,
221-- like 'Ahead', speculatively execute multiple stream actions in advance but
222-- return the results in their finishing order rather than in the stream
223-- traversal order. 'Parallel' is like 'Async' except that it provides
224-- unbounded parallelism instead of controlled parallelism.
225--
226-- For easy reference, we can classify the stream types based on /execution order/,
227-- /consumption order/, and /bounded or unbounded/ concurrency.
228-- Execution could be serial (i.e. synchronous) or asynchronous. In serial
229-- execution we execute the next action in the stream only after the previous
230-- one has finished executing. In asynchronous execution multiple actions in
231-- the stream can be executed asynchronously i.e. the next action can start
232-- executing even before the first one has finished. Consumption order
233-- determines the order in which the outputs generated by the composition are
234-- consumed. Consumption could be serial or asynchronous. In serial
235-- consumption, the outputs are consumed in the traversal order, in
236-- asynchronous consumption the outputs are consumed as they arrive i.e. first
237-- come first serve order.
238--
239-- +------------+--------------+--------------+--------------+
240-- | Type | Execution | Consumption | Concurrency |
241-- +============+==============+==============+==============+
242-- | 'Serial' | Serial | Serial | None |
243-- +------------+--------------+--------------+--------------+
244-- | 'Ahead' | Asynchronous | Serial | bounded |
245-- +------------+--------------+--------------+--------------+
246-- | 'Async' | Asynchronous | Asynchronous | bounded |
247-- +------------+--------------+--------------+--------------+
248-- | 'Parallel' | Asynchronous | Asynchronous | unbounded |
249-- +------------+--------------+--------------+--------------+
250--
251-- All these types can be freely inter-converted using type conversion
252-- combinators or type annotations, without any cost, to achieve the desired
253-- composition style. To force a particular type of composition, we coerce the
254-- stream type using the corresponding type adapting combinator from
255-- 'serially', 'aheadly', 'asyncly', or 'parallely'. The default stream type
256-- is inferred as 'Serial' unless you change it by using one of the combinators
257-- or by using a type annotation.
259-- $flavors
260--
261-- Streams can be combined using '<>' or 'mappend' to form a
262-- composite. Composite streams can be interpreted in a depth first or
263-- breadth first manner using an appropriate type conversion before
264-- consumption. Deep (e.g. 'Serial') stream type variants traverse a
265-- composite stream in a depth first manner, such that each stream is
266-- traversed fully before traversing the next stream. Wide
267-- (e.g. 'WSerial') stream types traverse it in a breadth first
268-- manner, such that one element from each stream is traversed before
269-- coming back to the first stream again.
270--
271-- Each stream type has a wide traversal variant prefixed by 'W'. The wide
272-- variant differs only in the Semigroup\/Monoid, Applicative\/Monad
273-- compositions of the streams.
274-- The following table summarizes the basic types and the corresponding wide
275-- variants:
276--
277-- @
278-- +------------+-----------+
279-- | Deep | Wide |
280-- +============+===========+
281-- | 'Serial' | 'WSerial' |
282-- +------------+-----------+
283-- | 'Ahead' | 'WAhead' |
284-- +------------+-----------+
285-- | 'Async' | 'WAsync' |
286-- +------------+-----------+
287-- @
288--
289-- Other than these types there are also 'ZipSerial' and 'ZipAsync' types that
290-- zip streams serially or concurrently using 'Applicative' operation. These
291-- types are not monads they are only applicatives and they do not differ in
292-- 'Semigroup' composition.
293--
295-- $programs
296--
297-- When writing concurrent programs it is advised to not use the concurrent
298-- style stream combinators blindly at the top level. That might create too
299-- much concurrency where it is not even required, and can even degrade
300-- performance in some cases. In some cases it can also lead to surprising
301-- behavior because of some code that is supposed to be serial becoming
302-- concurrent. Please be aware that all concurrency capable APIs that you may
303-- have used under the scope of a concurrent stream combinator will become
304-- concurrent. For example if you have a 'repeatM' somewhere in your program
305-- and you use 'parallely' on top, the 'repeatM' becomes fully parallel,
306-- resulting into an infinite parallel execution . Instead, use the
307-- /Keep It Serial and Stupid/ principle, start with the default serial
308-- composition and enable concurrent combinators only when and where necessary.
309-- When you use a concurrent combinator you can use an explicit 'serially'
310-- combinator to suppress any unnecessary concurrency under the scope of that
311-- combinator.
313-- $monadtransformers
314--
315-- To represent streams in an arbitrary monad use the more general monad
316-- transformer types for example the monad transformer type corresponding to
317-- the 'Serial' type is 'SerialT'. @SerialT m a@ represents a stream of values
318-- of type 'a' in some underlying monad 'm'. For example, @SerialT IO Int@ is a
319-- stream of 'Int' in 'IO' monad. In fact, the type 'Serial' is a synonym for
320-- @SerialT IO@.
321--
322-- Similarly we have monad transformer types for other stream types as well viz.
323-- 'WSerialT', 'AsyncT', 'WAsyncT' and 'ParallelT'.
324--
325-- To lift a value from an underlying monad in a monad transformer stack into a
326-- singleton stream use 'lift' and to lift from an IO action use 'liftIO'.
327--
328-- @
329-- > 'runStream' $ liftIO $ putStrLn "Hello world!"
330-- Hello world!
331-- > 'runStream' $ lift $ putStrLn "Hello world!"
332-- Hello world!
333-- @
334--
336-- $generating
337--
338-- We will assume the following imports in this tutorial. Go ahead, fire up a
339-- GHCi session and import these lines to start playing.
340--
341-- @
342-- > import "Streamly"
343-- > import "Streamly.Prelude" ((|:))
344-- > import qualified "Streamly.Prelude" as S
345-- @
346--
347-- 'nil' represents an empty stream and 'consM' or its operator form '|:' adds
348-- a monadic action at the head of the stream.
349--
350-- @
351-- > S.'toList' S.'nil'
352-- []
353-- > S.'toList' $ 'getLine' |: 'getLine' |: S.'nil'
354-- hello
355-- world
356-- ["hello","world"]
357-- @
358--
359-- To create a singleton stream from a pure value use 'yield' or 'pure' and to
360-- create a singleton stream from a monadic action use 'yieldM'. Note that in
361-- case of Zip applicative streams "pure" repeats the value to generate an
362-- infinite stream.
363--
364-- @
365-- > S.'toList' $ 'pure' 1
366-- [1]
367-- > S.'toList' $ 'yield' 1
368-- [1]
369-- > S.'toList' $ S.'yieldM' 'getLine'
370-- hello
371-- ["hello"]
372-- @
373--
374-- To create a stream from pure values in a 'Foldable' container use
375-- 'fromFoldable' which is equivalent to a fold using 'cons' and 'nil':
376--
377-- @
378-- > S.'toList' $ S.'fromFoldable' [1..3]
379-- [1,2,3]
380-- > S.'toList' $ 'Prelude.foldr' S.'cons' S.'nil' [1..3]
381-- [1,2,3]
382-- @
383--
384-- To create a stream from monadic actions in a 'Foldable' container just use a
385-- right fold using 'consM' and 'nil':
386--
387-- @
388-- > 'runStream' $ 'Prelude.foldr' ('|:') S.'nil' ['putStr' "Hello ", 'putStrLn' "world!"]
389-- Hello world!
390-- @
391--
392-- For more ways to construct a stream see the module "Streamly.Prelude".
394-- $generatingConcurrently
395--
396-- Monadic construction and generation functions like 'consM', 'unfoldrM',
397-- 'replicateM', 'repeatM', 'iterateM' and 'fromFoldableM' work concurrently
398-- when used with appropriate stream type combinator. The pure versions of
399-- these APIs are not concurrent, however you can use the monadic versions even
400-- for pure computations by wrapping the pure value in a monad to get the
401-- concurrent generation capability where required.
402--
403-- The following code finishes in 3 seconds (6 seconds when serial):
404--
405-- @
406-- > let p n = threadDelay (n * 1000000) >> return n
407-- > S.'toList' $ 'parallely' $ p 3 |: p 2 |: p 1 |: S.'nil'
408-- [1,2,3]
409-- > S.'toList' $ 'aheadly' $ p 3 |: p 2 |: p 1 |: S.'nil'
410-- [3,2,1]
411-- @
412-- The following finishes in 10 seconds (100 seconds when serial):
413--
414-- @
415-- > 'runStream' $ 'asyncly' $ S.'replicateM' 10 $ p 10
416-- @
417--
419-- $eliminating
420--
421-- We have already seen 'runStream' and 'toList' to eliminate a stream in the
422-- examples above. 'runStream' runs a stream discarding the results i.e. only
423-- for effects. 'toList' runs the stream and collects the results in a list.
424--
425-- For other ways to eliminate a stream see the @Folding@ section in
426-- "Streamly.Prelude" module.
428-- $transformation
429--
430-- Transformation over a stream is the equivalent of a @for@ loop construct in
431-- imperative paradigm. We iterate over every element in the stream and perform
432-- certain transformations for each element. Transformations may involve
433-- mapping functions over the elements, filtering elements from the stream or
434-- folding all the elements in the stream into a single value. Streamly streams
435-- are exactly like lists and you can perform all the transformations in the
436-- same way as you would on lists.
437--
438-- Here is a simple console echo program that just echoes every input line,
439-- forever:
440--
441-- @
442-- > 'runStream' $ S.'repeatM' getLine & S.'mapM' putStrLn
443-- @
444--
445-- The following code snippet reads lines from standard input, filters blank
446-- lines, drops the first non-blank line, takes the next two, up cases them,
447-- numbers them and prints them:
448--
449-- @
450-- import "Streamly"
451-- import qualified "Streamly.Prelude" as S
452-- import Data.Char (toUpper)
453-- import Data.Function ((&))
454--
455-- main = 'runStream' $
456-- S.'repeatM' getLine
457-- & S.'filter' (not . null)
458-- & S.'drop' 1
459-- & S.'take' 2
460-- & fmap (map toUpper)
461-- & S.'zipWith' (\\n s -> show n ++ " " ++ s) (S.'fromFoldable' [1..])
462-- & S.'mapM' putStrLn
463-- @
465-- $concurrentTransformation
466--
467-- Monadic transformation functions 'mapM' and 'sequence' work concurrently
468-- when used with appropriate stream type combinators. The pure versions do not
469-- work concurrently, however you can use the monadic versions even for pure
470-- computations to get the concurrent transformation capability where required.
471--
472-- This would print a value every second (2 seconds when serial):
473--
474-- @
475-- > let p n = threadDelay (n * 1000000) >> return n
476-- > runStream $ aheadly $ S.'mapM' (\\x -> p 1 >> print x) (serially $ repeatM (p 1))
477-- @
478--
480-- $concurrentApplication
481--
482-- The concurrent function application operators '|$' and '|&' apply a stream
483-- argument to a stream function concurrently to compose a concurrent pipeline
484-- of stream processing functions:
485--
486-- Because both the stages run concurrently, we would see a delay of only 1
487-- second instead of 2 seconds in the following:
488--
489-- @
490-- > let p n = threadDelay (n * 1000000) >> return n
491-- > runStream $ S.'repeatM' (p 1) '|&' S.'mapM' (\\x -> p 1 >> print x)
492-- @
494-- $semigroup
495--
496-- We can combine two streams into a single stream using semigroup composition
497-- operation '<>'. Streams can be combined in many different ways as described
498-- in the following sections, the '<>' operation behaves differently depending
499-- on the stream type in effect. The stream type and therefore the composition
500-- style can be changed at any point using one of the type combinators as
501-- discussed earlier.
503-- $imports
504--
505-- In most of example snippets we do not repeat the imports. Where imports are
506-- not explicitly specified use the imports shown below.
507--
508-- @
509-- import "Streamly"
510-- import "Streamly.Prelude" ((|:), nil)
511-- import qualified "Streamly.Prelude" as S
512--
513-- import Control.Concurrent
514-- import Control.Monad (forever)
515-- @
516--
517-- To illustrate concurrent vs serial composition aspects, we will use the
518-- following @delay@ function to introduce a sleep or delay specified in
519-- seconds. After the delay it prints the number of seconds it slept.
520--
521-- @
522-- delay n = S.'yieldM' $ do
523-- threadDelay (n * 1000000)
524-- tid \<- myThreadId
525-- putStrLn (show tid ++ ": Delay " ++ show n)
526-- @
528-- $serial
529--
530-- The 'Semigroup' operation '<>' of the 'Serial' type combines the two streams
531-- in a /serial depth first/ manner. We use the 'serially' type combinator to
532-- effect 'Serial' style of composition. We can also use an explicit 'Serial'
533-- type annotation for the stream to achieve the same effect. However, since
534-- 'Serial' is the default type unless explicitly specified by using a
535-- combinator, we can omit using an explicit combinator or type annotation for
536-- this style of composition.
537--
538-- When two streams with multiple elements are combined in this manner, the
539-- monadic actions in the two streams are performed sequentially i.e. first all
540-- actions in the first stream are performed sequentially and then all actions
541-- in the second stream are performed sequentially. We call it
542-- /serial depth first/ as the full depth of one stream is fully traversed
543-- before we move to the next. The following example prints the sequence 1, 2,
544-- 3, 4:
545--
546-- @
547-- main = 'runStream' $ (print 1 |: print 2 |: nil) <> (print 3 |: print 4 |: nil)
548-- @
549-- @
550-- 1
551-- 2
552-- 3
553-- 4
554-- @
555--
556-- All actions in both the streams are performed serially in the same thread.
557-- In the following example we can see that all actions are performed in the
558-- same thread and take a combined total of @3 + 2 + 1 = 6@ seconds:
559--
560-- @
561-- main = 'runStream' $ delay 3 <> delay 2 <> delay 1
562-- @
563-- @
564-- ThreadId 36: Delay 3
565-- ThreadId 36: Delay 2
566-- ThreadId 36: Delay 1
567-- @
568--
569-- The polymorphic version of the binary operation '<>' of the 'Serial' type is
570-- 'serial'. We can use 'serial' to join streams in a sequential manner
571-- irrespective of the type of stream:
572--
573-- @
574-- main = 'runStream' $ (print 1 |: print 2 |: nil) \`serial` (print 3 |: print 4 |: nil)
575-- @
577-- $interleaved
578--
579-- The 'Semigroup' operation '<>' of the 'WSerial' type combines the two
580-- streams in a /serial breadth first/ manner. We use the 'wSerially' type
581-- combinator to effect 'WSerial' style of composition. We can also use the
582-- 'WSerial' type annotation for the stream to achieve the same effect.
583--
584-- When two streams with multiple elements are combined in this manner, we
585-- traverse all the streams in a breadth first manner i.e. one action from each
586-- stream is performed and yielded to the resulting stream before we come back
587-- to the first stream again and so on.
588-- The following example prints the sequence 1, 3, 2, 4
589--
590-- @
591-- main = 'runStream' . 'wSerially' $ (print 1 |: print 2 |: nil) <> (print 3 |: print 4 |: nil)
592-- @
593-- @
594-- 1
595-- 3
596-- 2
597-- 4
598-- @
599--
600-- Even though the monadic actions of the two streams are performed in an
601-- interleaved manner they are all performed serially in the same thread. In
602-- the following example we can see that all actions are performed in the same
603-- thread and take a combined total of @3 + 2 + 1 = 6@ seconds:
604--
605-- @
606-- main = 'runStream' . 'wSerially' $ delay 3 <> delay 2 <> delay 1
607-- @
608-- @
609-- ThreadId 36: Delay 3
610-- ThreadId 36: Delay 2
611-- ThreadId 36: Delay 1
612-- @
613--
614-- The polymorphic version of the 'WSerial' binary operation '<>' is called
615-- 'wSerial'. We can use 'wSerial' to join streams in an interleaved manner
616-- irrespective of the type, notice that we have not used the 'wSerially'
617-- combinator in the following example:
618--
619-- @
620-- main = 'runStream' $ (print 1 |: print 2 |: nil) \`wSerial` (print 3 |: print 4 |: nil)
621-- @
622-- @
623-- 1
624-- 3
625-- 2
626-- 4
627-- @
628--
629-- Note that this composition cannot be used to fold infinite number of streams
630-- since it requires preserving the state until a stream is finished.
632-- $ahead
633--
634-- The 'Semigroup' operation '<>' of the 'Ahead' type combines two streams in a
635-- /serial depth first/ manner with concurrent lookahead. We use the 'aheadly'
636-- type combinator to effect 'Ahead' style of composition. We can also use an
637-- explicit 'Ahead' type annotation for the stream to achieve the same effect.
638--
639-- When two streams are combined in this manner, the streams are traversed in
640-- depth first manner just like 'Serial', however it can execute the next
641-- stream concurrently and keep the results ready when its turn arrives.
642-- Concurrent execution of the next stream(s) is performed if the first stream
643-- blocks or if it cannot produce output at the rate that is enough to meet the
644-- consumer demand. Multiple streams can be executed concurrently to meet the
645-- demand. The following example would print the result in a second even
646-- though each action in each stream takes one second:
647--
648-- @
649-- main = do
650-- xs \<- 'toList' . 'aheadly' $ (p 1 |: p 2 |: nil) <> (p 3 |: p 4 |: nil)
651-- print xs
652-- where p n = threadDelay 1000000 >> return n
653-- @
654-- @
655-- [1,2,3,4]
656-- @
657--
658-- Each stream is constructed 'aheadly' and then both the streams are merged
659-- 'aheadly', therefore, all the actions can run concurrently but the result is
660-- presented in serial order.
661--
662-- You can also use the polymorphic combinator 'ahead' in place of '<>' to
663-- compose any type of streams in this manner.
665-- $async
666--
667-- The 'Semigroup' operation '<>' of the 'Async' type combines the two
668-- streams in a depth first manner with parallel look ahead. We use the
669-- 'asyncly' type combinator to effect 'Async' style of composition. We
670-- can also use the 'Async' type annotation for the stream type to achieve
671-- the same effect.
672--
673-- When two streams with multiple elements are combined in this manner, the
674-- streams are traversed in depth first manner just like 'Serial', however it
675-- can execute the next stream concurrently and return the results from it
676-- as they arrive i.e. the results from the next stream may be yielded even
677-- before the results from the first stream. Concurrent execution of the next
678-- stream(s) is performed if the first stream blocks or if it cannot produce
679-- output at the rate that is enough to meet the consumer demand. Multiple
680-- streams can be executed concurrently to meet the demand.
681-- In the example below each element in the stream introduces a constant delay
682-- of 1 second, however, it takes just one second to produce all the results.
683-- The results are not guaranteed to be in any particular order:
684--
685-- @
686-- main = do
687-- xs \<- 'runStream' . 'asyncly' $ (p 1 |: p 2 |: nil) <> (p 3 |: p 4 |: nil)
688-- print xs
689-- where p n = threadDelay 1000000 >> return n
690-- @
691-- @
692-- [4,2,1,3]
693-- @
694--
695-- The constituent streams are also composed in 'Async' manner and the
696-- composition of streams too. We can compose the constituent streams to run
697-- serially, in that case it would take 2 seconds to produce all the results.
698-- The elements in the serial streams would be in serial order in the results:
699--
700-- @
701-- main = do
702-- xs \<- 'runStream' . 'asyncly' $ (serially $ p 1 |: p 2 |: nil) <> (serially $ p 3 |: p 4 |: nil)
703-- print xs
704-- where p n = threadDelay 1000000 >> return n
705-- @
706-- @
707-- [3,1,2,4]
708-- @
709--
710-- In the following example we can see that new threads are started when a
711-- computation blocks. Notice that the output from the stream with the
712-- shortest delay is printed first. The whole computation takes @maximum of
713-- (3, 2, 1) = 3@ seconds:
714--
715-- @
716-- main = 'runStream' . 'asyncly' $ delay 3 '<>' delay 2 '<>' delay 1
717-- @
718-- @
719-- ThreadId 42: Delay 1
720-- ThreadId 41: Delay 2
721-- ThreadId 40: Delay 3
722-- @
723--
724-- When we have a tree of computations composed using this style, the tree is
725-- traversed in DFS style just like the 'Serial' style, the only difference is
726-- that here we can move on to executing the next stream if a stream blocks.
727-- However, we will not start new threads if we have sufficient output to
728-- saturate the consumer. This is why we call it left-biased demand driven or
729-- adaptive concurrency style, the concurrency tends to stay on the left side
730-- of the composition as long as possible. More threads are started based on
731-- the pull rate of the consumer. The following example prints an output every
732-- second as all of the actions are concurrent.
733--
734-- @
735-- main = 'runStream' . 'asyncly' $ (delay 1 <> delay 2) <> (delay 3 <> delay 4)
736-- @
737-- @
738-- 1
739-- 2
740-- 3
741-- 4
742-- @
743--
744-- All the computations may even run in a single thread when more threads are
745-- not needed. As you can see, in the following example the computations are
746-- run in a single thread one after another, because none of them blocks.
747-- However, if the thread consuming the stream were faster than the producer
748-- then it would have started parallel threads for each computation to keep up
749-- even if none of them blocks:
750--
751-- @
752-- main = 'runStream' . 'asyncly' $ traced (sqrt 9) '<>' traced (sqrt 16) '<>' traced (sqrt 25)
753-- where traced m = S.'yieldM' (myThreadId >>= print) >> return m
754-- @
755-- @
756-- ThreadId 40
757-- ThreadId 40
758-- ThreadId 40
759-- @
760--
761-- Note that the order of printing in the above examples may change due to
762-- variations in scheduling latencies for concurrent threads.
763--
764-- The polymorphic version of the 'Async' binary operation '<>' is called
765-- 'async'. We can use 'async' to join streams in a left biased
766-- adaptively concurrent manner irrespective of the type, notice that we have
767-- not used the 'asyncly' combinator in the following example:
768--
769-- @
770-- main = 'runStream' $ delay 3 \`async` delay 2 \`async` delay 1
771-- @
772-- @
773-- ThreadId 42: Delay 1
774-- ThreadId 41: Delay 2
775-- ThreadId 40: Delay 3
776-- @
777--
778-- Since the concurrency provided by this operator is demand driven it cannot
779-- be used when the composed computations start timers that are relative to
780-- each other because all computations may not be started at the same time and
781-- therefore timers in all of them may not start at the same time. When
782-- relative timing among all computations is important or when we need to start
783-- all computations at once for any reason 'Parallel' style must be used
784-- instead.
785--
786-- 'Async' style should be preferred over 'Parallel' or 'WAsync' unless you
787-- really need those. It utilizes the resources optimally. It should be used
788-- when we know that the computations can run in parallel but we do not care if
789-- they actually run in parallel or not, that decision can be left to the
790-- scheduler based on demand. Also, note that this operator can be used to fold
791-- infinite number of streams in contrast to the 'Parallel' or 'WAsync' styles,
792-- because it does not require us to run all of them at the same time in a fair
793-- manner.
795-- $wasync
796--
797-- The 'Semigroup' operation '<>' of the 'WAsync' type combines two streams in
798-- a concurrent manner using /breadth first traversal/. We use the 'wAsyncly'
799-- type combinator to effect 'WAsync' style of composition. We can also use the
800-- 'WAsync' type annotation for the stream to achieve the same effect.
801--
802-- When streams with multiple elements are combined in this manner, we traverse
803-- all the streams concurrently in a breadth first manner i.e. one action from
804-- each stream is performed and yielded to the resulting stream before we come
805-- back to the first stream again and so on. Even though we execute the actions
806-- in a breadth first order the outputs are consumed on a first come first
807-- serve basis.
808--
809-- In the following example we can see that outputs are produced in the breadth
810-- first traversal order but this is not guaranteed.
811--
812-- @
813-- main = 'runStream' . 'wAsyncly' $ (serially $ print 1 |: print 2 |: nil) <> (serially $ print 3 |: print 4 |: nil)
814-- @
815-- @
816-- 1
817-- 3
818-- 2
819-- 4
820-- @
821--
822-- The polymorphic version of the binary operation '<>' of the 'WAsync' type is
823-- 'wAsync'. We can use 'wAsync' to join streams using a breadth first
824-- concurrent traversal irrespective of the type, notice that we have not used
825-- the 'wAsyncly' combinator in the following example:
826--
827-- @
828-- main = 'runStream' $ delay 3 \`wAsync` delay 2 \`wAsync` delay 1
829-- @
830-- @
831-- ThreadId 42: Delay 1
832-- ThreadId 41: Delay 2
833-- ThreadId 40: Delay 3
834-- @
835--
836-- Since the concurrency provided by this style is demand driven it may not
837-- be used when the composed computations start timers that are relative to
838-- each other because all computations may not be started at the same time and
839-- therefore timers in all of them may not start at the same time. When
840-- relative timing among all computations is important or when we need to start
841-- all computations at once for any reason 'Parallel' style must be used
842-- instead.
843--
845-- $parallel
846--
847-- The 'Semigroup' operation '<>' of the 'Parallel' type combines the two
848-- streams in a fairly concurrent manner with round robin scheduling. We use
849-- the 'parallely' type combinator to effect 'Parallel' style of composition.
850-- We can also use the 'Parallel' type annotation for the stream type to
851-- achieve the same effect.
852--
853-- When two streams with multiple elements are combined in this manner, the
854-- monadic actions in both the streams are performed concurrently with a fair
855-- round robin scheduling. The outputs are yielded in the order in which the
856-- actions complete. This is pretty similar to the 'WAsync' type, the
857-- difference is that 'WAsync' is adaptive to the consumer demand and may or
858-- may not execute all actions in parallel depending on the demand, whereas
859-- 'Parallel' runs all the streams in parallel irrespective of the demand.
860--
861-- The following example sends a query to all the three search engines in
862-- parallel and prints the name of the search engines in the order in which the
863-- responses arrive:
864--
865-- @
866-- import "Streamly"
867-- import qualified Streamly.Prelude as S
868-- import Network.HTTP.Simple
869--
870-- main = 'runStream' . 'parallely' $ google \<> bing \<> duckduckgo
871-- where
872-- google = get "https://www.google.com/search?q=haskell"
873-- bing = get "https://www.bing.com/search?q=haskell"
874-- duckduckgo = get "https://www.duckduckgo.com/?q=haskell"
875-- get s = S.'yieldM' (httpNoBody (parseRequest_ s) >> putStrLn (show s))
876-- @
877--
878-- The polymorphic version of the binary operation '<>' of the 'Parallel' type
879-- is 'parallel'. We can use 'parallel' to join streams in a fairly concurrent
880-- manner irrespective of the type, notice that we have not used the
881-- 'parallely' combinator in the following example:
882--
883-- @
884-- main = 'runStream' $ delay 3 \`parallel` delay 2 \`wAsync` delay 1
885-- @
886-- @
887-- ThreadId 42: Delay 1
888-- ThreadId 41: Delay 2
889-- ThreadId 40: Delay 3
890-- @
891--
892-- Note that this style of composition cannot be used to combine infinite
893-- number of streams, as it will lead to an infinite sized scheduling queue.
894--
896-- XXX to be removed
897-- $custom
898--
899-- The 'mkAsync' API can be used to create references to asynchronously running
900-- stream computations. We can then use 'uncons' to explore the streams
901-- arbitrarily and then recompose individual elements to create a new stream.
902-- This way we can dynamically decide which stream to explore at any given
903-- time. Take an example of a merge sort of two sorted streams. We need to
904-- keep consuming items from the stream which has the lowest item in the sort
905-- order. This can be achieved using async references to streams. See
906-- "MergeSort.hs" in the examples directory.
908-- $monoid
909--
910-- We can use 'Monoid' instances to fold a container of streams in the desired
911-- style using 'fold' or 'foldMap'. We have also provided some fold utilities
912-- to fold streams using the polymorphic combine operations:
913--
914-- * 'foldWith' is like 'fold', it folds a 'Foldable' container of streams
915-- using the given composition operator.
916-- * 'foldMapWith' is like 'foldMap', it folds like @foldWith@ but also maps a
917-- function before folding.
918-- * 'forEachWith' is like @foldMapwith@ but the container argument comes before
919-- the function argument.
920--
921-- All of the following are equivalent and start ten concurrent tasks each with
922-- a delay from 1 to 10 seconds, resulting in the printing of each number every
923-- second:
924--
925-- @
926-- import "Streamly"
927-- import qualified "Streamly.Prelude" as S
928-- import Control.Concurrent
929--
930-- main = do
931-- 'runStream' $ 'asyncly' $ foldMap delay [1..10]
932-- 'runStream' $ 'foldWith' 'async' (map delay [1..10])
933-- 'runStream' $ 'foldMapWith' 'async' delay [1..10]
934-- 'runStream' $ 'forEachWith' 'async' [1..10] delay
935-- where delay n = S.'yieldM' $ threadDelay (n * 1000000) >> print n
936-- @
938-- $nesting
939--
940-- Till now we discussed ways to apply transformations on a stream or to merge
941-- streams together to create another stream. We mentioned earlier that
942-- transforming a stream is similar to a @for@ loop in the imperative paradigm.
943-- We will now discuss the concept of a nested composition of streams which is
944-- analogous to nested @for@ loops in the imperative paradigm. Functional
945-- programmers call this style of composition a list transformer or @ListT@.
946-- Logic programmers call it a logic monad or non-deterministic composition,
947-- but for ordinary imperative minded people like me it is easier to think in
948-- terms of good old nested @for@ loops.
949--
950-- $monad
951--
952-- In functional programmer's parlance the 'Monad' instances of different
953-- 'IsStream' types implement non-determinism, exploring all possible
954-- combination of choices from both the streams. From an imperative
955-- programmer's point of view it behaves like nested loops i.e. for each
956-- element in the first stream and for each element in the second stream
957-- execute the body of the loop.
958--
959-- The 'Monad' instances of 'Serial', 'WSerial', 'Async' and 'WAsync'
960-- stream types support different flavors of nested looping. In other words,
961-- they are all variants of list transformer. The nesting behavior of these
962-- types correspond exactly to the way they merge streams as we discussed in
963-- the previous section.
964--
966-- $regularSerial
967--
968-- The 'Monad' composition of the 'Serial' type behaves like a standard list
969-- transformer. This is the default when we do not use an explicit type
970-- combinator. However, the 'serially' type combinator can be used to switch to
971-- this style of composition. We will see how this style of composition works
972-- in the following examples.
973--
974-- Let's start with an example with a simple @for@ loop without any nesting.
975-- For simplicity of illustration we are using streams of pure values in all
976-- the examples. However, the streams could also be made of monadic actions
977-- instead.
978--
979-- @
980-- import "Streamly"
981-- import qualified "Streamly.Prelude" as S
982--
983-- main = 'runStream' $ do
984-- x <- S.'fromFoldable' [3,2,1]
985-- delay x
986-- @
987-- @
988-- ThreadId 30: Delay 3
989-- ThreadId 30: Delay 2
990-- ThreadId 30: Delay 1
991-- @
992--
993-- As we can see, the code after the @fromFoldable@ statement is run three
994-- times, once for each value of @x@ drawn from the stream. All the three
995-- iterations are serial and run in the same thread one after another. In
996-- imperative terms this is equivalent to a @for@ loop with three iterations.
997--
998-- A console echo loop copying standard input to standard output can simply be
999-- written like this:
1001-- @
1002-- import "Streamly"
1003-- import qualified "Streamly.Prelude" as S
1005-- main = 'runStream' $ forever $ S.yieldM getLine >>= S.yieldM . putStrLn
1006-- @
1008-- When multiple streams are composed using this style they nest in a DFS
1009-- manner i.e. nested iterations of a loop are executed before we proceed to
1010-- the next iteration of the parent loop. This behaves just like nested @for@
1011-- loops in imperative programming.
1013-- @
1014-- import "Streamly"
1015-- import qualified "Streamly.Prelude" as S
1017-- main = 'runStream' $ do
1018-- x <- S.'fromFoldable' [1,2]
1019-- y <- S.'fromFoldable' [3,4]
1020-- S.'yieldM' $ putStrLn $ show (x, y)
1021-- @
1022-- @
1023-- (1,3)
1024-- (1,4)
1025-- (2,3)
1026-- (2,4)
1027-- @
1029-- Notice that this is analogous to merging streams of type 'Serial' or merging
1030-- streams using 'serial'.
1032-- $aheadNesting
1034-- The 'Monad' composition of 'Ahead' type behaves just like 'Serial' except
1035-- that it can speculatively perform a bounded number of next iterations of a
1036-- loop concurrently.
1038-- The 'aheadly' type combinator can be used to switch to this style of
1039-- composition. Alternatively, a type annotation can be used to specify the
1040-- type of the stream as 'Ahead'.
1042-- @
1043-- import "Streamly"
1044-- import "Streamly.Prelude" as S
1046-- comp = 'toList' . 'aheadly' $ do
1047-- x <- S.'fromFoldable' [3,2,1]
1048-- delay x >> return x
1050-- main = comp >> print
1051-- @
1052-- @
1053-- ThreadId 40: Delay 1
1054-- ThreadId 39: Delay 2
1055-- ThreadId 38: Delay 3
1056-- [3,2,1]
1057-- @
1059-- This code finishes in 3 seconds, 'Serial' would take 6 seconds. As we can
1060-- see all the three iterations are concurrent and run in different threads,
1061-- however, the results are returned in the serial order.
1063-- Concurrency is demand driven, when multiple streams are composed using this
1064-- style, the iterations are executed in a depth first manner just like
1065-- 'Serial' i.e. nested iterations are executed before we proceed to the next
1066-- outer iteration. The only difference is that we may execute multiple future
1067-- iterations concurrently and keep the results ready.
1070-- $concurrentNesting
1072-- The 'Monad' composition of 'Async' type can perform the iterations of a
1073-- loop concurrently. Concurrency is demand driven i.e. more concurrent
1074-- iterations are started only if the previous iterations are not able to
1075-- produce enough output for the consumer of the output stream. This works
1076-- exactly the same way as the merging of two streams 'asyncly' works.
1077-- This is the concurrent analogue of 'Serial' style monadic composition.
1079-- The 'asyncly' type combinator can be used to switch to this style of
1080-- composition. Alternatively, a type annotation can be used to specify the
1081-- type of the stream as 'Async'.
1083-- @
1084-- import "Streamly"
1085-- import "Streamly.Prelude"
1087-- main = 'runStream' . 'asyncly' $ do
1088-- x <- S.'fromFoldable' [3,2,1]
1089-- delay x
1090-- @
1091-- @
1092-- ThreadId 40: Delay 1
1093-- ThreadId 39: Delay 2
1094-- ThreadId 38: Delay 3
1095-- @
1097-- As we can see the code after the @fromFoldable@ statement is run three
1098-- times, once for each value of @x@. All the three iterations are concurrent
1099-- and run in different threads. The iteration with least delay finishes first.
1100-- When compared to imperative programming, this can be viewed as a @for@ loop
1101-- with three concurrent iterations.
1103-- Concurrency is demand driven just as in the case of 'async' merging.
1104-- When multiple streams are composed using this style, the iterations are
1105-- triggered in a depth first manner just like 'Serial' i.e. nested iterations are
1106-- executed before we proceed to the next iteration at higher level. However,
1107-- unlike 'Serial' more than one iterations may be started concurrently based
1108-- on the demand from the consumer of the stream.
1110-- @
1111-- import "Streamly"
1112-- import qualified "Streamly.Prelude" as S
1114-- main = 'runStream' . 'asyncly' $ do
1115-- x <- S.'fromFoldable' [1,2]
1116-- y <- S.'fromFoldable' [3,4]
1117-- S.'yieldM' $ putStrLn $ show (x, y)
1118-- @
1119-- @
1120-- (1,3)
1121-- (1,4)
1122-- (2,3)
1123-- (2,4)
1124-- @
1126-- $interleavedNesting
1128-- The 'Monad' composition of 'WSerial' type interleaves the iterations of
1129-- outer and inner loops in a nested loop composition. This works exactly the
1130-- same way as the merging of two streams in 'wSerially' fashion works. The
1131-- 'wSerially' type combinator can be used to switch to this style of
1132-- composition. Alternatively, a type annotation can be used to specify the
1133-- type of the stream as 'WSerial'.
1135-- @
1136-- import "Streamly"
1137-- import qualified "Streamly.Prelude" as S
1139-- main = 'runStream' . 'wSerially' $ do
1140-- x <- S.'fromFoldable' [1,2]
1141-- y <- S.'fromFoldable' [3,4]
1142-- S.yieldM $ putStrLn $ show (x, y)
1143-- @
1144-- @
1145-- (1,3)
1146-- (2,3)
1147-- (1,4)
1148-- (2,4)
1149-- @
1152-- $wasyncNesting
1154-- Just like 'Async' the 'Monad' composition of 'WAsync' runs the iterations of
1155-- a loop concurrently. The difference is in the nested loop behavior. The
1156-- nested loops in this type are traversed and executed in a breadth first
1157-- manner rather than the depth first manner of 'Async' style.
1158-- The loop nesting works exactly the same way as the merging of streams
1159-- 'wAsyncly' works. The 'wAsyncly' type combinator can be used to switch to
1160-- this style of composition. Alternatively, a type annotation can be used to
1161-- specify the type of the stream as 'WAsync'.
1163-- @
1164-- import "Streamly"
1165-- import qualified "Streamly.Prelude" as S
1167-- main = 'runStream' . 'wAsyncly' $ do
1168-- x <- S.'fromFoldable' [1,2]
1169-- y <- S.'fromFoldable' [3,4]
1170-- S.'yieldM' $ putStrLn $ show (x, y)
1171-- @
1172-- @
1173-- (1,3)
1174-- (2,3)
1175-- (1,4)
1176-- (2,4)
1177-- @
1179-- $parallelNesting
1181-- Just like 'Async' or 'WAsync' the 'Monad' composition of 'Parallel' runs the
1182-- iterations of a loop concurrently. The difference is in the nested loop
1183-- behavior. The streams at each nest level is run fully concurrently
1184-- irrespective of the demand. The loop nesting works exactly the same way as
1185-- the merging of streams 'parallely' works. The 'parallely' type combinator
1186-- can be used to switch to this style of composition. Alternatively, a type
1187-- annotation can be used to specify the type of the stream as 'Parallel'.
1189-- @
1190-- import "Streamly"
1191-- import qualified "Streamly.Prelude" as S
1193-- main = 'runStream' . 'parallely' $ do
1194-- x <- S.'fromFoldable' [3,2,1]
1195-- delay x
1196-- @
1197-- @
1198-- ThreadId 40: Delay 1
1199-- ThreadId 39: Delay 2
1200-- ThreadId 38: Delay 3
1201-- @
1203-- $monadExercise
1205-- Streamly code is usually written in a way that is agnostic of the
1206-- specific monadic composition type. We use a polymorphic type with a
1207-- 'IsStream' type class constraint. When running the stream we can choose the
1208-- specific mode of composition. For example take a look at the following code.
1210-- @
1211-- import "Streamly"
1212-- import qualified "Streamly.Prelude" as S
1214-- composed :: (IsStream t, Monad (t IO)) => t IO ()
1215-- composed = do
1216-- sz <- sizes
1217-- cl <- colors
1218-- sh <- shapes
1219-- S.'yieldM' $ putStrLn $ show (sz, cl, sh)
1221-- where
1223-- sizes = S.'fromFoldable' [1, 2, 3]
1224-- colors = S.'fromFoldable' ["red", "green", "blue"]
1225-- shapes = S.'fromFoldable' ["triangle", "square", "circle"]
1226-- @
1228-- Now we can interpret this in whatever way we want:
1230-- @
1231-- main = 'runStream' . 'serially' $ composed
1232-- main = 'runStream' . 'wSerially' $ composed
1233-- main = 'runStream' . 'asyncly' $ composed
1234-- main = 'runStream' . 'wAsyncly' $ composed
1235-- main = 'runStream' . 'parallely' $ composed
1236-- @
1238-- As an exercise try to figure out the output of this code for each mode of
1239-- composition.
1241-- $functor
1243-- 'fmap' transforms a stream by mapping a function on all elements of the
1244-- stream. 'fmap' behaves in the same way for all stream types, it is always
1245-- serial.
1247-- @
1248-- import "Streamly"
1249-- import qualified "Streamly.Prelude" as S
1251-- main = (S.'toList' $ fmap show $ S.'fromFoldable' [1..10]) >>= print
1252-- @
1254-- Also see the 'mapM' and 'sequence' functions for mapping actions, in the
1255-- "Streamly.Prelude" module.
1257-- $applicative
1259-- Applicative is precisely the same as the 'ap' operation of 'Monad'. For
1260-- zipping applicatives separate types 'ZipSerial' and 'ZipAsync' are
1261-- provided.
1263-- The following example uses the 'Serial' applicative, it runs all iterations
1264-- serially and takes a total 17 seconds (1 + 3 + 4 + 2 + 3 + 4):
1266-- @
1267-- import "Streamly"
1268-- import qualified "Streamly.Prelude" as S
1269-- import Control.Concurrent
1271-- s1 = d 1 <> d 2
1272-- s2 = d 3 <> d 4
1273-- d n = delay n >> return n
1275-- main = (S.'toList' . 'serially' $ (,) \<$> s1 \<*> s2) >>= print
1276-- @
1277-- @
1278-- ThreadId 36: Delay 1
1279-- ThreadId 36: Delay 3
1280-- ThreadId 36: Delay 4
1281-- ThreadId 36: Delay 2
1282-- ThreadId 36: Delay 3
1283-- ThreadId 36: Delay 4
1284-- [(1,3),(1,4),(2,3),(2,4)]
1285-- @
1287-- Similarly 'WSerial' applicative runs the iterations in an interleaved
1288-- order but since it is serial it takes a total of 17 seconds:
1290-- @
1291-- main = (S.'toList' . 'wSerially' $ (,) \<$> s1 \<*> s2) >>= print
1292-- @
1293-- @
1294-- ThreadId 36: Delay 1
1295-- ThreadId 36: Delay 3
1296-- ThreadId 36: Delay 2
1297-- ThreadId 36: Delay 3
1298-- ThreadId 36: Delay 4
1299-- ThreadId 36: Delay 4
1300-- [(1,3),(2,3),(1,4),(2,4)]
1301-- @
1303-- 'Async' can run the iterations concurrently and therefore takes a total
1304-- of 10 seconds (1 + 2 + 3 + 4):
1306-- @
1307-- main = (S.'toList' . 'asyncly' $ (,) \<$> s1 \<*> s2) >>= print
1308-- @
1309-- @
1310-- ThreadId 34: Delay 1
1311-- ThreadId 36: Delay 2
1312-- ThreadId 35: Delay 3
1313-- ThreadId 36: Delay 3
1314-- ThreadId 35: Delay 4
1315-- ThreadId 36: Delay 4
1316-- [(1,3),(2,3),(1,4),(2,4)]
1317-- @
1319-- Similarly 'WAsync' as well can run the iterations concurrently and
1320-- therefore takes a total of 10 seconds (1 + 2 + 3 + 4):
1322-- @
1323-- main = (S.'toList' . 'wAsyncly' $ (,) \<$> s1 \<*> s2) >>= print
1324-- @
1325-- @
1326-- ThreadId 34: Delay 1
1327-- ThreadId 36: Delay 2
1328-- ThreadId 35: Delay 3
1329-- ThreadId 36: Delay 3
1330-- ThreadId 35: Delay 4
1331-- ThreadId 36: Delay 4
1332-- [(1,3),(2,3),(1,4),(2,4)]
1333-- @
1335-- $zipping
1337-- Zipping is a special transformation where the corresponding elements of two
1338-- streams are combined together using a zip function producing a new stream of
1339-- outputs. Two different types are provided for serial and concurrent zipping.
1340-- These types provide an applicative instance that can be used to lift
1341-- functions to zip the argument streams.
1342-- Also see the zipping functions in the "Streamly.Prelude" module.
1344-- $serialzip
1346-- The applicative instance of 'ZipSerial' type zips streams serially.
1347-- 'zipSerially' type combinator can be used to switch to serial applicative
1348-- zip composition:
1350-- @
1351-- import "Streamly"
1352-- import qualified "Streamly.Prelude" as S
1353-- import Control.Concurrent
1355-- d n = delay n >> return n
1356-- s1 = 'serially' $ d 1 <> d 2
1357-- s2 = 'serially' $ d 3 <> d 4
1359-- main = (S.'toList' . 'zipSerially' $ (,) \<$> s1 \<*> s2) >>= print
1360-- @
1362-- This takes total 10 seconds to zip, which is (1 + 2 + 3 + 4) since
1363-- everything runs serially:
1365-- @
1366-- ThreadId 29: Delay 1
1367-- ThreadId 29: Delay 3
1368-- ThreadId 29: Delay 2
1369-- ThreadId 29: Delay 4
1370-- [(1,3),(2,4)]
1371-- @
1373-- $parallelzip
1375-- The applicative instance of 'ZipAsync' type zips streams concurrently.
1376-- 'zipAsyncly' type combinator can be used to switch to parallel applicative
1377-- zip composition:
1380-- @
1381-- import "Streamly"
1382-- import qualified "Streamly.Prelude" as S
1383-- import Control.Concurrent
1384-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
1386-- d n = delay n >> return n
1387-- s1 = 'serially' $ d 1 <> d 2
1388-- s2 = 'serially' $ d 3 <> d 4
1390-- main = do
1391-- hSetBuffering stdout LineBuffering
1392-- (S.'toList' . 'zipAsyncly' $ (,) \<$> s1 \<*> s2) >>= print
1393-- @
1395-- This takes 7 seconds to zip, which is max (1,3) + max (2,4) because 1 and 3
1396-- are produced concurrently, and 2 and 4 are produced concurrently:
1398-- @
1399-- ThreadId 32: Delay 1
1400-- ThreadId 32: Delay 2
1401-- ThreadId 33: Delay 3
1402-- ThreadId 33: Delay 4
1403-- [(1,3),(2,4)]
1404-- @
1406-- $concurrent
1408-- When writing concurrent programs there are two distinct places where the
1409-- programmer can control the concurrency. First, when /composing/ a stream by
1410-- merging multiple streams we can choose an appropriate sum style operators to
1411-- combine them concurrently or serially. Second, when /processing/ a stream in
1412-- a monadic composition we can choose one of the monad composition types to
1413-- choose the desired type of concurrency.
1415-- In the following example the squares of @x@ and @y@ are computed
1416-- concurrently using the 'async' operation and the square roots of their
1417-- sum are computed serially because of the 'streamly' combinator. We can
1418-- choose different combinators for the monadic processing and the stream
1419-- generation, to control the concurrency. We can also use the 'asyncly'
1420-- combinator instead of explicitly folding with 'async'.
1422-- @
1423-- import "Streamly"
1424-- import qualified "Streamly.Prelude" as S
1425-- import Data.List (sum)
1427-- main = do
1428-- z \<- S.'toList'
1429-- $ 'serially' -- Serial monadic processing (sqrt below)
1430-- $ do
1431-- x2 \<- 'forEachWith' 'async' [1..100] $ -- Concurrent @"for"@ loop
1432-- \\x -> return $ x * x -- body of the loop
1433-- y2 \<- 'forEachWith' 'async' [1..100] $
1434-- \\y -> return $ y * y
1435-- return $ sqrt (x2 + y2)
1436-- print $ sum z
1437-- @
1439-- We can see how this directly maps to the imperative style
1440-- <https://en.wikipedia.org/wiki/OpenMP OpenMP> model, we use combinators
1441-- and operators instead of the ugly pragmas.
1443-- For more concurrent programming examples see,
1444-- <src/examples/ListDir.hs ListDir.hs>,
1445-- <src/examples/MergeSort.hs MergeSort.hs> and
1446-- <src/examples/SearchQuery.hs SearchQuery.hs> in the examples directory.
1448-- $reactive
1450-- Reactive programming is nothing but concurrent streaming which is what
1451-- streamly is all about. With streamly we can generate streams of events,
1452-- merge streams that are generated concurrently and process events
1453-- concurrently. We can do all this without any knowledge about the specifics
1454-- of the implementation of concurrency. In the following example you will see
1455-- that the code is just regular Haskell code without much streamly APIs used
1456-- (active hyperlinks are the streamly APIs) and yet it is a reactive
1457-- application.
1459-- This application has two independent and concurrent sources of event
1460-- streams, @acidRain@ and @userAction@. @acidRain@ continuously generates
1461-- events that deteriorate the health of the character in the game.
1462-- @userAction@ can be "potion" or "quit". When the user types "potion" the
1463-- health improves and the game continues.
1465-- @
1466-- {-\# LANGUAGE FlexibleContexts #-}
1468-- import "Streamly"
1469-- import "Streamly.Prelude" as S
1470-- import Control.Monad (void, when)
1471-- import Control.Monad.IO.Class (MonadIO(liftIO))
1472-- import Control.Monad.State (MonadState, get, modify, runStateT, put)
1474-- data Event = Quit | Harm Int | Heal Int deriving (Show)
1476-- userAction :: MonadAsync m => 'SerialT' m Event
1477-- userAction = S.'repeatM' $ liftIO askUser
1478-- where
1479-- askUser = do
1480-- command <- getLine
1481-- case command of
1482-- "potion" -> return (Heal 10)
1483-- "harm" -> return (Harm 10)
1484-- "quit" -> return Quit
1485-- _ -> putStrLn "Type potion or harm or quit" >> askUser
1487-- acidRain :: MonadAsync m => 'SerialT' m Event
1488-- acidRain = 'asyncly' $ 'constRate' 1 $ S.'repeatM' $ liftIO $ return $ Harm 1
1490-- data Result = Check | Done
1492-- runEvents :: (MonadAsync m, MonadState Int m) => 'SerialT' m Result
1493-- runEvents = do
1494-- event \<- userAction \`parallel` acidRain
1495-- case event of
1496-- Harm n -> modify (\\h -> h - n) >> return Check
1497-- Heal n -> modify (\\h -> h + n) >> return Check
1498-- Quit -> return Done
1500-- data Status = Alive | GameOver deriving Eq
1502-- getStatus :: (MonadAsync m, MonadState Int m) => Result -> m Status
1503-- getStatus result =
1504-- case result of
1505-- Done -> liftIO $ putStrLn "You quit!" >> return GameOver
1506-- Check -> do
1507-- h <- get
1508-- liftIO $ if (h <= 0)
1509-- then putStrLn "You die!" >> return GameOver
1510-- else putStrLn ("Health = " <> show h) >> return Alive
1512-- main :: IO ()
1513-- main = do
1514-- putStrLn "Your health is deteriorating due to acid rain,\\
1515-- \\ type \\"potion\\" or \\"quit\\""
1516-- let runGame = S.'runWhile' (== Alive) $ S.'mapM' getStatus runEvents
1517-- void $ runStateT runGame 60
1518-- @
1520-- You can also find the source of this example in the examples directory as
1521-- <src/examples/AcidRain.hs AcidRain.hs>. It has been adapted from Gabriel's
1522-- <https://hackage.haskell.org/package/pipes-concurrency-2.0.8/docs/Pipes-Concurrent-Tutorial.html pipes-concurrency>
1523-- package.
1524-- This is much simpler compared to the pipes version because of the builtin
1525-- concurrency in streamly. You can also find a SDL based reactive programming
1526-- example adapted from Yampa in
1527-- <src/examples/CirclingSquare.hs CirclingSquare.hs>.
1529-- $performance
1531-- Streamly is highly optimized for performance, it is designed for serious
1532-- high performing, concurrent and scalable applications. We have created the
1533-- <https://hackage.haskell.org/package/streaming-benchmarks streaming-benchmarks>
1534-- package which is specifically and carefully designed to measure the
1535-- performance of Haskell streaming libraries fairly and squarely in the right
1536-- way. Streamly performs at par or even better than most streaming libraries
1537-- for serial operations even though it needs to deal with the concurrency
1538-- capability.
1540-- $interop
1542-- We can use @unfoldr@ and @uncons@ to convert one streaming type to another.
1544-- Interop with @vector@:
1546-- @
1547-- import Streamly
1548-- import qualified Streamly.Prelude as S
1549-- import qualified Data.Vector.Fusion.Stream.Monadic as V
1551-- -- | vector to streamly
1552-- fromVector :: (IsStream t, Monad m) => V.Stream m a -> t m a
1553-- fromVector = S.unfoldrM unconsV
1554-- where
1555-- unconsV v = do
1556-- r <- V.null v
1557-- if r
1558-- then return Nothing
1559-- else do
1560-- h <- V.head v
1561-- return $ Just (h, V.tail v)
1563-- -- | streamly to vector
1564-- toVector :: Monad m => SerialT m a -> V.Stream m a
1565-- toVector = V.unfoldrM (S.uncons . adapt)
1567-- main = do
1568-- S.toList (fromVector (V.fromList [1..3])) >>= print
1569-- V.toList (toVector (S.fromFoldable [1..3])) >>= print
1570-- @
1572-- Interop with @pipes@:
1574-- @
1575-- import "Streamly"
1576-- import qualified "Streamly.Prelude" as S
1577-- import qualified Pipes as P
1578-- import qualified Pipes.Prelude as P
1580-- -- | pipes to streamly
1581-- fromPipes :: (IsStream t, Monad m) => P.Producer a m r -> t m a
1582-- fromPipes = S.'unfoldrM' unconsP
1583-- where
1584-- -- Adapt P.next to return a Maybe instead of Either
1585-- unconsP p = P.next p >>= either (\\_ -> return Nothing) (return . Just)
1587-- -- | streamly to pipes
1588-- toPipes :: Monad m => SerialT m a -> P.Producer a m ()
1589-- toPipes = P.unfoldr unconsS
1590-- where
1591-- -- Adapt S.uncons to return an Either instead of Maybe
1592-- unconsS s = S.'uncons' s >>= maybe (return $ Left ()) (return . Right)
1594-- main = do
1595-- S.'toList' (fromPipes (P.each [1..3])) >>= print
1596-- P.toListM (toPipes (S.'fromFoldable' [1..3])) >>= print
1597-- @
1599-- Interop with @streaming@:
1601-- @
1602-- import "Streamly"
1603-- import qualified "Streamly.Prelude" as S
1604-- import qualified Streaming as SG
1605-- import qualified Streaming.Prelude as SG
1607-- -- | streaming to streamly
1608-- fromStreaming :: (IsStream t, Monad m) => SG.Stream (SG.Of a) m r -> t m a
1609-- fromStreaming = S.unfoldrM SG.uncons
1611-- -- | streamly to streaming
1612-- toStreaming :: Monad m => SerialT m a -> SG.Stream (SG.Of a) m ()
1613-- toStreaming = SG.unfoldr unconsS
1614-- where
1615-- -- Adapt S.uncons to return an Either instead of Maybe
1616-- unconsS s = S.'uncons' s >>= maybe (return $ Left ()) (return . Right)
1618-- main = do
1619-- S.toList (fromStreaming (SG.each [1..3])) >>= print
1620-- SG.toList (toStreaming (S.fromFoldable [1..3])) >>= print
1621-- @
1623-- Interop with @conduit@:
1625-- @
1626-- import "Streamly"
1627-- import qualified "Streamly.Prelude" as S
1628-- import qualified Data.Conduit as C
1629-- import qualified Data.Conduit.List as C
1630-- import qualified Data.Conduit.Combinators as C
1632-- -- It seems there is no way out of a conduit as it does not provide an
1633-- -- uncons or a tail function. We can convert streamly to conduit though.
1635-- -- | streamly to conduit
1636-- toConduit :: Monad m => SerialT m a -> C.ConduitT i a m ()
1637-- toConduit s = C.unfoldM S.'uncons' s
1639-- main = do
1640-- C.runConduit (toConduit (S.'fromFoldable' [1..3]) C..| C.sinkList) >>= print
1641-- @
1643-- $comparison
1645-- List transformers and logic programming monads also provide a product style
1646-- composition similar to streamly, however streamly generalizes it with the
1647-- time dimension; allowing streams to be composed in an asynchronous and
1648-- concurrent fashion in many different ways. It also provides multiple
1649-- alternative ways of composing streams e.g. serial, interleaved or
1650-- concurrent.
1652-- This seemingly simple addition of asynchronicity and concurrency to product
1653-- style streaming composition unifies a number of disparate abstractions into
1654-- one powerful, concise and elegant abstraction. A wide variety of
1655-- programming problems can be solved elegantly with this abstraction. In
1656-- particular, it unifies three major programming domains namely
1657-- non-deterministic (logic) programming, concurrent programming and functional
1658-- reactive programming. In other words, you can do everything with this one
1659-- abstraction that you could do with the popular libraries listed under these
1660-- categories in the list below.
1662-- @
1663-- +-----------------+----------------+
1664-- | Non-determinism | <https://hackage.haskell.org/package/pipes pipes> |
1665-- | +----------------+
1666-- | | <https://hackage.haskell.org/package/list-t list-t> |
1667-- | +----------------+
1668-- | | <https://hackage.haskell.org/package/logict logict> |
1669-- +-----------------+----------------+
1670-- | Streaming | <https://hackage.haskell.org/package/vector vector> |
1671-- | +----------------+
1672-- | | <https://hackage.haskell.org/package/streaming streaming> |
1673-- | +----------------+
1674-- | | <https://hackage.haskell.org/package/pipes pipes> |
1675-- | +----------------+
1676-- | | <https://hackage.haskell.org/package/conduit conduit> |
1677-- +-----------------+----------------+
1678-- | Concurrency | <https://hackage.haskell.org/package/async async> |
1679-- | +----------------+
1680-- | | <https://hackage.haskell.org/package/transient transient> |
1681-- +-----------------+----------------+
1682-- | FRP | <https://hackage.haskell.org/package/Yampa Yampa> |
1683-- | +----------------+
1684-- | | <https://hackage.haskell.org/package/dunai dunai> |
1685-- | +----------------+
1686-- | | <https://hackage.haskell.org/package/reflex reflex> |
1687-- +-----------------+----------------+
1688-- @
1690-- Streamly is a list-transformer. It provides all the functionality provided
1691-- by any of the list transformer and logic programming packages listed above.
1692-- In addition, Streamly naturally integrates the concurrency dimension to the
1693-- basic list transformer functionality.
1695-- When it comes to streaming, in terms of the streaming API streamly is almost
1696-- identical to the vector package. Streamly, vector and streaming packages all
1697-- represent a stream as data and are therefore similar in the fundamental
1698-- approach to streaming. The fundamental difference is that streamly adds
1699-- concurrency support and the monad instance provides concurrent looping.
1700-- Other streaming libraries like pipes, conduit and machines represent and
1701-- compose stream processors rather than the stream data and therefore fall in
1702-- another class of streaming libraries and have comparatively more complicated
1703-- types.
1705-- When it comes to concurrency, streamly can do everything that the @async@
1706-- package can do and more. async provides applicative concurrency whereas
1707-- streamly provides both applicative and monadic concurrency. The
1708-- 'ZipAsync' type behaves like the applicative instance of async. In
1709-- comparison to transient streamly has a first class streaming interface and
1710-- is a monad transformer that can be used universally in any Haskell monad
1711-- transformer stack. Streamly was in fact originally inspired by the
1712-- concurrency implementation in @transient@ though it has no resemblance with
1713-- that and takes a lazy pull approach versus transient's strict push approach.
1715-- The non-determinism, concurrency and streaming combination make streamly a
1716-- strong FRP capable library as well. FRP is fundamentally stream of events
1717-- that can be processed concurrently. The example in this tutorial as well as
1718-- the "Streamly.Examples.CirclingSquare" example from Yampa demonstrate the
1719-- basic FRP capability of streamly. In core concepts streamly is strikingly
1720-- similar to @dunai@. dunai was designed from a FRP perspective and streamly
1721-- was originally designed from a concurrency perspective. However, both have
1722-- similarity at the core.
1724-- $furtherReading
1726-- * Read the documentation of "Streamly" module
1727-- * Read the documentation of "Streamly.Prelude" module
1728-- * See the examples in the "examples" directory of the package
1729-- * See the tests in the "test" directory of the package