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