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