on ec6ab34 help sign in
streamly/src/Streamly/Tutorial.hs
1009 lines 35 kB history raw
1{-# OPTIONS_GHC -fno-warn-unused-imports #-}
2-- |
3-- Module : Streamly.Tutorial
4-- Copyright : (c) 2017 Harendra Kumar
5--
6-- License : BSD3
7-- Maintainer : harendra.kumar@gmail.com
8--
9-- Streamly, short for stream concurrently, combines the essence of
10-- non-determinism, streaming and concurrency in functional programming.
11-- Concurrent and non-concurrent applications are almost indistinguisable,
12-- concurrency capability does not at all impact the performance of
13-- non-concurrent case.
14-- Streaming enables writing modular, composable and scalable applications with
15-- ease and concurrency allows you to make them scale and perform well.
16-- Streamly enables writing concurrent applications without being aware of
17-- threads or synchronization. No explicit thread control is needed, where
18-- applicable the concurrency rate is automatically controlled based on the
19-- demand by the consumer. However, combinators are provided to fine tune the
20-- concurrency control.
21-- Streaming and concurrency together enable expressing reactive applications
22-- conveniently. See "Streamly.Examples" for a simple SDL based FRP example.
23--
24-- Streamly streams are very much like the Haskell lists and most of the
25-- functions that work on lists have a counterpart that works on streams.
26-- However, streamly streams can be generated, consumed or combined
27-- concurrently. In this tutorial we will go over the basic concepts and how to
28-- use the library. The documentation of @Streamly@ module has more details on
29-- core APIs. For more APIs for constructing, folding, filtering, mapping and
30-- zipping etc. see the documentation of "Streamly.Prelude" module. For
31-- examples and other ways to use the library see the module
32-- "Streamly.Examples" as well.
34module Streamly.Tutorial
35 (
36 -- * Streams
37 -- $streams
39 -- ** Generating Streams
40 -- $generating
42 -- ** Eliminating Streams
43 -- $eliminating
45 -- * Combining Streams
46 -- $combining
48 -- ** Semigroup Style
49 -- $semigroup
51 -- *** Serial composition ('<>')
52 -- $serial
54 -- *** Async composition ('<|')
55 -- $parallel
57 -- *** Interleaved composition ('<=>')
58 -- $interleaved
60 -- *** Fair Concurrent composition ('<|>')
61 -- $fairParallel
63 -- *** Custom composition
64 -- $custom
66 -- ** Monoid Style
67 -- $monoid
69 -- * Transforming Streams
70 -- $transforming
72 -- ** Monad
73 -- $monad
75 -- *** Serial Composition ('StreamT')
76 -- $regularSerial
78 -- *** Async Composition ('AsyncT')
79 -- $concurrentNesting
81 -- *** Interleaved Composition ('InterleavedT')
82 -- $interleavedNesting
84 -- *** Fair Concurrent Composition ('ParallelT')
85 -- $fairlyConcurrentNesting
87 -- *** Exercise
88 -- $monadExercise
90 -- ** Applicative
91 -- $applicative
93 -- ** Functor
94 -- $functor
96 -- * Zipping Streams
97 -- $zipping
99 -- ** Serial Zipping
100 -- $serialzip
102 -- ** Parallel Zipping
103 -- $parallelzip
105 -- * Summary of Compositions
106 -- $compositionSummary
108 -- * Concurrent Programming
109 -- $concurrent
111 -- * Reactive Programming
112 -- $reactive
114 -- * Performance
115 -- $performance
117 -- * Interoperation with Streaming Libraries
118 -- $interop
120 -- * Comparison with Existing Packages
121 -- $comparison
122 )
123where
125import Streamly
126import Streamly.Prelude
127import Data.Semigroup
128import Control.Applicative
129import Control.Monad
130import Control.Monad.IO.Class (MonadIO(..))
131import Control.Monad.Trans.Class (MonadTrans (lift))
133-- $streams
134--
135-- Streamly provides many different stream types depending on the desired
136-- composition style. The simplest type is 'StreamT'. 'StreamT' is a monad
137-- transformer, the type @StreamT m a@ represents a stream of values of type
138-- 'a' in some underlying monad 'm'. For example, @StreamT IO Int@ is a stream
139-- of 'Int' in 'IO' monad.
141-- $generating
142--
143-- Pure values can be placed into the stream type using 'return' or 'pure'.
144-- Effects in the IO monad can be lifted to the stream type using the 'liftIO'
145-- combinator. In a transformer stack we can lift actions from the lower monad
146-- using the 'lift' combinator. Some examples of streams with a single element:
147--
148-- @
149-- return 1 :: 'StreamT' IO Int
150-- @
151-- @
152-- liftIO $ putStrLn "Hello world!" :: 'StreamT' IO ()
153-- @
154--
155-- We can combine streams using '<>' to create streams of many elements:
156--
157-- @
158-- return 1 <> return 2 <> return 3 :: 'StreamT' IO Int
159-- @
160--
161-- For more ways to construct or generate a stream see the module
162-- "Streamly.Prelude".
164-- $eliminating
165--
166-- 'runStreamT' runs a composed 'StreamT' computation, lowering the type into
167-- the underlying monad and discarding the result stream:
168--
169-- @
170-- import "Streamly"
171--
172-- main = 'runStreamT' $ liftIO $ putStrLn "Hello world!"
173-- @
174--
175-- 'toList' runs a stream computation and collects the result stream in a list
176-- in the underlying monad. 'toList' is a polymorphic function that works on
177-- multiple stream types belonging to the class 'Streaming'. Therefore, before
178-- you run a stream you need to tell how you want to interpret the stream by
179-- using one of the stream type combinators ('serially', 'asyncly', 'parallely'
180-- etc.). The combinator 'serially' is equivalent to annotating the type as @::
181-- StreamT@.
182--
183-- @
184-- import "Streamly"
185--
186-- main = do
187-- xs \<- 'toList' $ 'serially' $ return 1 <> return 2
188-- print xs
189-- @
190--
191-- For other ways to eliminate or fold a stream see the module
192-- "Streamly.Prelude".
194-- $semigroup
195-- Streams of the same type can be combined into a composite stream in many
196-- different ways using one of the semigroup style binary composition operators
197-- i.e. '<>', '<=>', '<|', '<|>', 'mplus'. These operators work on all stream
198-- types ('StreamT', 'AsyncT' etc.) uniformly.
199--
200-- To illustrate the concurrent aspects, we will use the following @delay@
201-- function to introduce a delay specified in seconds.
202--
203-- @
204-- import "Streamly"
205-- import Control.Concurrent
206--
207-- delay n = liftIO $ do
208-- threadDelay (n * 1000000)
209-- tid \<- myThreadId
210-- putStrLn (show tid ++ ": Delay " ++ show n)
211-- @
213-- $serial
214--
215-- We have already seen, the '<>' operator. It composes two streams in series
216-- i.e. the first stream is completely exhausted and then the second stream is
217-- processed. The following example prints the sequence 3, 2, 1 and takes a
218-- total of 6 seconds because everything is serial:
219--
220-- @
221-- main = 'runStreamT' $ delay 3 <> delay 2 <> delay 1
222-- @
223-- @
224-- ThreadId 36: Delay 3
225-- ThreadId 36: Delay 2
226-- ThreadId 36: Delay 1
227-- @
229-- $interleaved
230-- The '<=>' operator is serial like '<>' but it interleaves the two streams
231-- i.e. it yields one element from the first stream and then one element from
232-- the second stream, and so on. The following example prints the sequence 1,
233-- 3, 2, 4 and takes a total of 10 seconds because everything is serial:
234--
235-- @
236-- main = 'runStreamT' $ (delay 1 <> delay 2) '<=>' (delay 3 <> delay 4)
237-- @
238-- @
239-- ThreadId 36: Delay 1
240-- ThreadId 36: Delay 3
241-- ThreadId 36: Delay 2
242-- ThreadId 36: Delay 4
243-- @
244--
245-- Note that this operator cannot be used to fold infinite containers since it
246-- requires preserving the state until a stream is finished.
248-- $parallel
249--
250-- The '<|' operator can run both computations concurrently, /when needed/.
251-- In the following example since the first computation blocks we start the
252-- next one in a separate thread and so on:
253--
254-- @
255-- main = 'runStreamT' $ delay 3 '<|' delay 2 '<|' delay 1
256-- @
257-- @
258-- ThreadId 42: Delay 1
259-- ThreadId 41: Delay 2
260-- ThreadId 40: Delay 3
261-- @
262--
263-- This is the concurrent version of the '<>' operator. The computations are
264-- triggered in the same order as '<>' except that they are concurrent. When
265-- we have a tree of computations composed using this operator, the tree is
266-- traversed in DFS style just like '<>'.
267--
268-- @
269-- main = 'runStreamT' $ (p 1 '<|' p 2) '<|' (p 3 '<|' p 4)
270-- where p = liftIO . print
271-- @
272-- @
273-- 1
274-- 2
275-- 3
276-- 4
277-- @
278--
279-- Concurrency provided by this operator is demand driven. The second
280-- computation is run concurrently with the first only if the first computation
281-- is not producing enough output to keep the stream consumer busy otherwise
282-- the second computation is run serially after the previous one. The number of
283-- concurrent threads is adapted dynamically based on the pull rate of the
284-- consumer of the stream.
285-- As you can see, in the following example the computations are run in a
286-- single thread one after another, because none of them blocks. However, if
287-- the thread consuming the stream were faster than the producer then it would
288-- have started parallel threads for each computation to keep up even if none
289-- of them blocks:
290--
291-- @
292-- main = 'runStreamT' $ traced (sqrt 9) '<|' traced (sqrt 16) '<|' traced (sqrt 25)
293-- @
294-- @
295-- ThreadId 40
296-- ThreadId 40
297-- ThreadId 40
298-- @
299--
300-- Since the concurrency provided by this operator is demand driven it cannot
301-- be used when the composed computations have timers that are relative to each
302-- other because all computations may not be started at the same time and
303-- therefore timers in all of them may not start at the same time. When
304-- relative timing among all computations is important or when we need to start
305-- all computations at once for some reason '<|>' must be used instead.
306-- However, '<|' is useful in situations when we want to optimally utilize the
307-- resources and we know that the computations can run in parallel but we do
308-- not care if they actually run in parallel or not, that decision is left to
309-- the scheduler. Also, note that this operator can be used to fold infinite
310-- containers in contrast to '<|>', because it does not require us to run all
311-- of them at the same time.
312--
313-- The left bias (or the DFS style) of the operator '<|' is suggested by its
314-- shape. You can also think of this as an unbalanced version of the fairly
315-- parallel operator '<|>'.
317-- $fairParallel
318--
319-- The 'Alternative' composition operator '<|>', like '<|', runs the composed
320-- computations concurrently. However, unlike '<|' it runs all of the
321-- computations in fairly parallel manner using a round robin scheduling
322-- mechanism. This can be considered as the concurrent version of the fairly
323-- interleaved serial operation '<=>'. Note that this cannot be used on
324-- infinite containers, as it will lead to an infinite sized scheduling queue.
325--
326-- The following example sends a query to three search engines in parallel and
327-- prints the name of the search engine as a response arrives:
328--
329-- @
330-- import "Streamly"
331-- import Network.HTTP.Simple
332--
333-- main = 'runStreamT' $ google \<|> bing \<|> duckduckgo
334-- where
335-- google = get "https://www.google.com/search?q=haskell"
336-- bing = get "https://www.bing.com/search?q=haskell"
337-- duckduckgo = get "https://www.duckduckgo.com/?q=haskell"
338-- get s = liftIO (httpNoBody (parseRequest_ s) >> putStrLn (show s))
339-- @
341-- $custom
342--
343-- The 'async' API can be used to create references to asynchronously running
344-- stream computations. We can then use 'uncons' to explore the streams
345-- arbitrarily and then recompose individual elements to create a new stream.
346-- This way we can dynamically decide which stream to explore at any given
347-- time. Take an example of a merge sort of two sorted streams. We need to
348-- keep consuming items from the stream which has the lowest item in the sort
349-- order. This can be achieved using async references to streams. See
350-- "Streamly.Examples.MergeSortedStreams".
352-- $monoid
353--
354-- Each of the semigroup compositions described has an identity that can be
355-- used to fold a possibly empty container. An empty stream is represented by
356-- 'nil' which can be represented in various standard forms as 'mempty',
357-- 'empty' or 'mzero'.
358-- Some fold utilities are also provided by the library for convenience:
359--
360-- * 'foldWith' folds a 'Foldable' container of stream computations using the
361-- given composition operator.
362-- * 'foldMapWith' folds like foldWith but also maps a function before folding.
363-- * 'forEachWith' is like foldMapwith but the container argument comes before
364-- the function argument.
365-- * The 'each' primitive from "Streamly.Prelude" folds a 'Foldable' container
366-- using the '<>' operator:
367--
368-- All of the following are equivalent:
369--
370-- @
371-- import "Streamly"
372--
373-- main = do
374-- 'toList' . 'serially' $ 'foldWith' (<>) (map return [1..10]) >>= print
375-- 'toList' . 'serially' $ 'foldMapWith' (<>) return [1..10] >>= print
376-- 'toList' . 'serially' $ 'forEachWith' (<>) [1..10] return >>= print
377-- 'toList' . 'serially' $ 'each' [1..10] >>= print
378-- @
380-- $transforming
381--
382-- The previous section discussed ways to merge the elements of two streams
383-- without doing any transformation on them. In this section we will explore
384-- how to transform streams using 'Functor', 'Applicative' or 'Monad' style
385-- compositions. The applicative and monad composition of all 'Streaming' types
386-- behave exactly the same way as a list transformer. For simplicity of
387-- illustration we are using streams of pure values in the following examples.
388-- However, the real application of streams arises when these streams are
389-- generated using monadic actions.
391-- $monad
392--
393-- In functional programmer's parlance the 'Monad' instance of 'Streaming'
394-- types implement non-determinism, exploring all possible combination of
395-- choices from both the streams. From an imperative programmer's point of view
396-- it behaves like nested loops i.e. for each element in the first stream and
397-- for each element in the second stream apply the body of the loop. If you are
398-- familiar with list transformer this behavior is exactly the same as that of
399-- a list transformer.
400--
401-- Just like we saw in sum style compositions earlier, monadic composition also
402-- has multiple variants each of which exactly corresponds to one of the sum
403-- style composition variant.
405-- $regularSerial
406--
407-- When we interpret the monadic composition as 'StreamT' we get a standard
408-- list transformer like serial composition.
409--
410-- @
411-- main = 'runStreamT' $ do
412-- x <- 'each' [3,2,1]
413-- delay x
414-- @
415-- @
416-- ThreadId 30: Delay 3
417-- ThreadId 30: Delay 2
418-- ThreadId 30: Delay 1
419-- @
420--
421-- As you can see the code after the @each@ statement is run three times, once
422-- for each value of @x@. All the three iterations are serial and run in the
423-- same thread one after another. When compared to imperative programming, this
424-- can also be viewed as a @for@ loop with three iterations.
425--
426-- When multiple streams are composed using this style they nest in a DFS
427-- manner i.e. nested iterations of an iteration are executed before we proceed
428-- to the next iteration at higher level. This behaves just like nested @for@
429-- loops in imperative programming.
430--
431-- @
432-- main = 'runStreamT' $ do
433-- x <- 'each' [1,2]
434-- y <- 'each' [3,4]
435-- liftIO $ putStrLn $ show (x, y)
436-- @
437-- @
438-- (1,3)
439-- (1,4)
440-- (2,3)
441-- (2,4)
442-- @
443--
444-- You will also notice that this is the monadic equivalent of the sum style
445-- composition using '<>'.
447-- $concurrentNesting
448--
449-- When we interpret the monadic composition as 'AsyncT' we get a /concurrent/
450-- list-transformer like composition. Multiple monadic continuations (or loop
451-- iterations) may be started concurrently. Concurrency is demand driven
452-- i.e. more concurrent iterations are started only if the previous iterations
453-- are not able to produce enough output for the consumer of the output stream.
454-- This is the concurrent version of 'StreamT'.
455--
456-- @
457-- main = 'runAsyncT' $ do
458-- x <- 'each' [3,2,1]
459-- delay x
460-- @
461-- @
462-- ThreadId 40: Delay 1
463-- ThreadId 39: Delay 2
464-- ThreadId 38: Delay 3
465-- @
466--
467-- As you can see the code after the @each@ statement is run three times, once
468-- for each value of @x@. All the three iterations are concurrent and run in
469-- different threads. The iteration with least delay finishes first. When
470-- compared to imperative programming, this can be viewed as a @for@ loop
471-- with three concurrent iterations.
472--
473-- Concurrency is demand driven just as in the case of '<|'. When multiple
474-- streams are composed using this style the iterations are triggered in a DFS
475-- manner just like 'StreamT' i.e. nested iterations are executed before we
476-- proceed to the next iteration at higher level. However, unlike 'StreamT'
477-- more than one iterations may be started concurrently, and based on the
478-- demand from the consumer.
479--
480-- @
481-- main = 'runAsyncT' $ do
482-- x <- 'each' [1,2]
483-- y <- 'each' [3,4]
484-- liftIO $ putStrLn $ show (x, y)
485-- @
486-- @
487-- (1,3)
488-- (1,4)
489-- (2,3)
490-- (2,4)
491-- @
492--
493-- You will notice that this is the monadic equivalent of the '<|' style
494-- sum composition. The same caveats apply to this as the '<|' operation.
496-- $interleavedNesting
497--
498-- When we interpret the monadic composition as 'InterleavedT' we get a serial
499-- but fairly interleaved list-transformer like composition. The monadic
500-- continuations or iterations of the outer loop are fairly interleaved with
501-- the continuations or iterations of the inner loop.
502--
503-- @
504-- main = 'runInterleavedT' $ do
505-- x <- 'each' [1,2]
506-- y <- 'each' [3,4]
507-- liftIO $ putStrLn $ show (x, y)
508-- @
509-- @
510-- (1,3)
511-- (2,3)
512-- (1,4)
513-- (2,4)
514-- @
515--
516-- You will notice that this is the monadic equivalent of the '<=>' style
517-- sum composition. The same caveats apply to this as the '<=>' operation.
519-- $fairlyConcurrentNesting
520--
521-- When we interpret the monadic composition as 'ParallelT' we get a
522-- /concurrent/ list-transformer like composition just like 'AsyncT'. The
523-- difference is that this is fully parallel with all iterations starting
524-- concurrently instead of the demand driven concurrency of 'AsyncT'.
525--
526-- @
527-- main = 'runParallelT' $ do
528-- x <- 'each' [3,2,1]
529-- delay x
530-- @
531-- @
532-- ThreadId 40: Delay 1
533-- ThreadId 39: Delay 2
534-- ThreadId 38: Delay 3
535-- @
536--
537-- You will notice that this is the monadic equivalent of the '<|>' style
538-- sum composition. The same caveats apply to this as the '<|>' operation.
540-- $monadExercise
541--
542-- The streamly code is usually written in a way that is agnostic of the
543-- specific monadic composition type. We use a polymorphic type with a
544-- 'Streaming' type class constraint. When running the stream we can choose the
545-- specific mode of composition. For example look at the following code.
546--
547-- @
548-- import "Streamly"
549--
550-- composed :: 'Streaming' t => t m a
551-- composed = do
552-- sz <- sizes
553-- cl <- colors
554-- sh <- shapes
555-- liftIO $ putStrLn $ show (sz, cl, sh)
556--
557-- where
558--
559-- sizes = 'each' [1, 2, 3]
560-- colors = 'each' ["red", "green", "blue"]
561-- shapes = 'each' ["triangle", "square", "circle"]
562-- @
563--
564-- Now we can interpret this in whatever way we want:
565--
566-- @
567-- main = 'runStreamT' composed
568-- main = 'runAsyncT' composed
569-- main = 'runInterleavedT' composed
570-- main = 'runParallelT' composed
571-- @
572--
573-- Equivalently, we can also write it using the type adapter combinators, like
574-- this:
575--
576-- @
577-- main = 'runStreaming' $ 'serially' $ composed
578-- main = 'runStreaming' $ 'asyncly' $ composed
579-- main = 'runStreaming' $ 'interleaving' $ composed
580-- main = 'runStreaming' $ 'parallely' $ composed
581-- @
582--
583-- As an exercise try to figure out the output of this code for each mode of
584-- composition.
586-- $functor
587--
588-- 'fmap' transforms a stream by mapping a function on all elements of the
589-- stream. The functor instance of each stream type defines 'fmap' to be
590-- precisely the same as 'liftM', and therefore 'fmap' is always serial
591-- irrespective of the type. For concurrent mapping, alternative versions of
592-- 'fmap', namely, 'asyncMap' and 'parMap' are provided.
593--
594-- @
595-- import "Streamly"
596--
597-- main = ('toList' $ 'serially' $ fmap show $ 'each' [1..10]) >>= print
598-- @
599--
600-- Also see the 'mapM' and 'sequence' functions for mapping actions, in the
601-- "Streamly.Prelude" module.
603-- $applicative
604--
605-- Applicative is precisely the same as the 'ap' operation of 'Monad'. For
606-- zipping and parallel applicatives separate types 'ZipStream' and 'ZipAsync'
607-- are provided.
608--
609-- The following example runs all iterations serially and takes a total 17
610-- seconds (1 + 3 + 4 + 2 + 3 + 4):
611--
612-- @
613-- import "Streamly"
614-- import "Streamly.Prelude"
615-- import Control.Concurrent
616--
617-- s1 = d 1 <> d 2
618-- s2 = d 3 <> d 4
619-- d n = delay n >> return n
620--
621-- main = ('toList' . 'serially' $ (,) \<$> s1 \<*> s2) >>= print
622-- @
623-- @
624-- ThreadId 36: Delay 1
625-- ThreadId 36: Delay 3
626-- ThreadId 36: Delay 4
627-- ThreadId 36: Delay 2
628-- ThreadId 36: Delay 3
629-- ThreadId 36: Delay 4
630-- [(1,3),(1,4),(2,3),(2,4)]
631-- @
632--
633-- Similalrly interleaving runs the iterations in an interleaved order but
634-- since it is serial it takes a total of 17 seconds:
635--
636-- @
637-- main = ('toList' . 'interleaving' $ (,) \<$> s1 \<*> s2) >>= print
638-- @
639-- @
640-- ThreadId 36: Delay 1
641-- ThreadId 36: Delay 3
642-- ThreadId 36: Delay 2
643-- ThreadId 36: Delay 3
644-- ThreadId 36: Delay 4
645-- ThreadId 36: Delay 4
646-- [(1,3),(2,3),(1,4),(2,4)]
647-- @
648--
649-- 'AsyncT' can run the iterations concurrently and therefore takes a total
650-- of 10 seconds (1 + 2 + 3 + 4):
651--
652-- @
653-- main = ('toList' . 'asyncly' $ (,) \<$> s1 \<*> s2) >>= print
654-- @
655-- @
656-- ThreadId 34: Delay 1
657-- ThreadId 36: Delay 2
658-- ThreadId 35: Delay 3
659-- ThreadId 36: Delay 3
660-- ThreadId 35: Delay 4
661-- ThreadId 36: Delay 4
662-- [(1,3),(2,3),(1,4),(2,4)]
663-- @
664--
665-- Similalrly 'ParallelT' as well can run the iterations concurrently and
666-- therefore takes a total of 10 seconds (1 + 2 + 3 + 4):
667--
668-- @
669-- main = ('toList' . 'parallely' $ (,) \<$> s1 \<*> s2) >>= print
670-- @
671-- @
672-- ThreadId 34: Delay 1
673-- ThreadId 36: Delay 2
674-- ThreadId 35: Delay 3
675-- ThreadId 36: Delay 3
676-- ThreadId 35: Delay 4
677-- ThreadId 36: Delay 4
678-- [(1,3),(2,3),(1,4),(2,4)]
679-- @
681-- $compositionSummary
682--
683-- The following table summarizes the types for monadic compositions and the
684-- operators for sum style compositions. This table captures the essence of
685-- streamly.
686--
687-- @
688-- +-----+--------------+------------+
689-- | | Serial | Concurrent |
690-- +=====+==============+============+
691-- | DFS | 'StreamT' | 'AsyncT' |
692-- | +--------------+------------+
693-- | | '<>' | '<|' |
694-- +-----+--------------+------------+
695-- | BFS | 'InterleavedT' | 'ParallelT' |
696-- | +--------------+------------+
697-- | | '<=>' | '<|>' |
698-- +-----+--------------+------------+
699-- @
701-- $zipping
702--
703-- Zipping is a special transformation where the corresponding elements of two
704-- streams are combined together using a zip function producing a new stream of
705-- outputs. Two different types are provided for serial and concurrent zipping.
706-- These types provide an applicative instance that zips the argument streams.
707-- Also see the zipping function in the "Streamly.Prelude" module.
709-- $serialzip
710--
711-- 'ZipStream' zips streams serially:
712--
713-- @
714-- import "Streamly"
715-- import "Streamly.Prelude"
716-- import Control.Concurrent
717--
718-- d n = delay n >> return n
719-- s1 = 'adapt' . 'serially' $ d 1 <> d 2
720-- s2 = 'adapt' . 'serially' $ d 3 <> d 4
721--
722-- main = ('toList' . 'zipping' $ (,) \<$> s1 \<*> s2) >>= print
723-- @
724--
725-- This takes total 10 seconds to zip, which is (1 + 2 + 3 + 4) since
726-- everything runs serially:
727--
728-- @
729-- ThreadId 29: Delay 1
730-- ThreadId 29: Delay 3
731-- ThreadId 29: Delay 2
732-- ThreadId 29: Delay 4
733-- [(1,3),(2,4)]
734-- @
736-- $parallelzip
737--
738-- 'ZipAsync' zips streams concurrently:
739--
740-- @
741-- import "Streamly"
742-- import "Streamly.Prelude"
743-- import Control.Concurrent
744-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
745--
746-- d n = delay n >> return n
747-- s1 = 'adapt' . 'serially' $ d 1 <> d 2
748-- s2 = 'adapt' . 'serially' $ d 3 <> d 4
749--
750-- main = do
751-- liftIO $ hSetBuffering stdout LineBuffering
752-- ('toList' . 'zippingAsync' $ (,) \<$> s1 \<*> s2) >>= print
753-- @
754--
755-- This takes 7 seconds to zip, which is max (1,3) + max (2,4) because 1 and 3
756-- are produced concurrently, and 2 and 4 are produced concurrently:
757--
758-- @
759-- ThreadId 32: Delay 1
760-- ThreadId 32: Delay 2
761-- ThreadId 33: Delay 3
762-- ThreadId 33: Delay 4
763-- [(1,3),(2,4)]
764-- @
766-- $concurrent
767--
768-- When writing concurrent programs there are two distinct places where the
769-- programmer chooses the type of concurrency. First, when /generating/ a
770-- stream by combining other streams we can use one of the sum style operators
771-- to combine them concurrently or serially. Second, when /processing/ a stream
772-- in a monadic composition we can choose one of the monad composition types to
773-- choose the desired type of concurrency.
774--
775-- In the following example the squares of @x@ and @y@ are computed
776-- concurrently using the '<|' operator and the square roots of their sum are
777-- also computed concurrently by using the 'asyncly' combinator. We can choose
778-- different combinators e.g. '<>' and 'serially', to control the concurrency.
779--
780-- @
781-- import "Streamly"
782-- import "Streamly.Prelude" (toList)
783-- import Data.List (sum)
784--
785-- main = do
786-- z \<- 'toList'
787-- $ 'asyncly' -- Concurrent monadic processing (sqrt below)
788-- $ do
789-- x2 \<- 'forEachWith' ('<|') [1..100] $ -- Concurrent @"for"@ loop
790-- \\x -> return $ x * x -- body of the loop
791-- y2 \<- 'forEachWith' ('<|') [1..100] $
792-- \\y -> return $ y * y
793-- return $ sqrt (x2 + y2)
794-- print $ sum z
795-- @
796--
797-- You can see how this directly maps to the imperative style
798-- <https://en.wikipedia.org/wiki/OpenMP OpenMP> model, we use combinators
799-- and operators instead of the ugly pragmas.
800--
801-- For more concurrent programming examples see,
802-- "Streamly.Examples.ListDirRecursive", "Streamly.Examples.MergeSortedStreams"
803-- and "Streamly.Examples.SearchEngineQuery".
805-- $reactive
806--
807-- Reactive programming is nothing but concurrent streaming which is what
808-- streamly is all about. With streamly we can generate streams of events,
809-- merge streams that are generated concurrently and process events
810-- concurrently. We can do all this without any knowledge about the specifics
811-- of the implementation of concurrency. In the following example you will see
812-- that the code is just regular Haskell code without much streamly APIs used
813-- (active hyperlinks are the streamly APIs) and yet it is a reactive
814-- application.
815--
816--
817-- This application has two independent and concurrent sources of event
818-- streams, @acidRain@ and @userAction@. @acidRain@ continuously generates
819-- events that deteriorate the health of the game character. @userAction@ can
820-- be "potion" or "quit". When the user types "potion" the health improves and
821-- the game continues.
822--
823-- @
824-- {-\# LANGUAGE FlexibleContexts #-}
825--
826-- import "Streamly"
827-- import Control.Concurrent (threadDelay)
828-- import Control.Monad (when)
829-- import Control.Monad.State
830-- import Data.Semigroup (cycle1)
831--
832-- data Event = Harm Int | Heal Int | Quit deriving (Show)
833--
834-- userAction :: MonadIO m => 'StreamT' m Event
835-- userAction = cycle1 $ liftIO askUser
836-- where
837-- askUser = do
838-- command <- getLine
839-- case command of
840-- "potion" -> return (Heal 10)
841-- "quit" -> return Quit
842-- _ -> putStrLn "What?" >> askUser
843--
844-- acidRain :: MonadIO m => 'StreamT' m Event
845-- acidRain = cycle1 $ liftIO (threadDelay 1000000) >> return (Harm 1)
846--
847-- game :: ('MonadAsync' m, MonadState Int m) => 'StreamT' m ()
848-- game = do
849-- event \<- userAction \<|> acidRain
850-- case event of
851-- Harm n -> modify $ \\h -> h - n
852-- Heal n -> modify $ \\h -> h + n
853-- Quit -> fail "quit"
854--
855-- h <- get
856-- when (h <= 0) $ fail "You die!"
857-- liftIO $ putStrLn $ "Health = " ++ show h
858--
859-- main = do
860-- putStrLn "Your health is deteriorating due to acid rain,\\
861-- \\ type \\"potion\\" or \\"quit\\""
862-- _ <- runStateT ('runStreamT' game) 60
863-- return ()
864-- @
865--
866-- You can also find the source of this example in
867-- "Streamly.Examples.AcidRainGame". It has been adapted from Gabriel's
868-- <https://hackage.haskell.org/package/pipes-concurrency-2.0.8/docs/Pipes-Concurrent-Tutorial.html pipes-concurrency>
869-- package.
870-- This is much simpler compared to the pipes version because of the builtin
871-- concurrency in streamly. You can also find a SDL based reactive programming
872-- example adapted from Yampa in "Streamly.Examples.CirclingSquare".
874-- $performance
875--
876-- Streamly is highly optimized for performance, it is designed for serious
877-- high performing, concurrent and scalable applications. We have created the
878-- <https://hackage.haskell.org/package/streaming-benchmarks streaming-benchmarks>
879-- package which is specifically and carefully designed to measure the
880-- performance of Haskell streaming libraries fairly and squarely in the right
881-- way. Streamly performs at par or even better than most streaming libraries
882-- for common operations even though it needs to deal with the concurrency
883-- capability.
885-- $interop
886--
887-- We can use @unfoldr@ and @uncons@ to convert one streaming type to another.
888-- We will assume the following common code to be available in the examples
889-- demonstrated below.
890--
891-- @
892-- import Streamly
893-- import Streamly.Prelude
894-- import System.IO (stdin)
895--
896-- -- Adapt uncons to return an Either instead of Maybe
897-- unconsE s = 'uncons' s >>= maybe (return $ Left ()) (return . Right)
898-- stdinLn = 'serially' $ 'fromHandle' stdin
899-- @
900--
901-- Interop with @pipes@:
902--
903-- @
904-- import qualified Pipes as P
905-- import qualified Pipes.Prelude as P
906--
907-- main = do
908-- -- streamly to pipe
909-- P.runEffect $ P.for (P.unfoldr unconsE stdinLn) (lift . putStrLn)
910--
911-- -- pipe to streamly
912-- -- Adapt P.next to return a Maybe instead of Either
913-- let nextM p = P.next p >>= either (\\_ -> return Nothing) (return . Just)
914-- runStreamT $ unfoldrM nextM P.stdinLn >>= lift . putStrLn
915-- @
916--
917-- Interop with @streaming@:
918--
919-- @
920-- import qualified Streaming as S
921-- import qualified Streaming.Prelude as S
922--
923-- main = do
924-- -- streamly to streaming
925-- S.stdoutLn $ S.unfoldr unconsE stdinLn
926--
927-- -- streaming to streamly
928-- runStreamT $ unfoldrM S.uncons S.stdinLn >>= lift . putStrLn
929--
930-- @
931--
932-- Interop with @conduit@:
933--
934-- @
935-- import qualified Data.Conduit as C
936-- import qualified Data.Conduit.List as C
937-- import qualified Data.Conduit.Combinators as C
938--
939-- -- streamly to conduit
940-- main = (C.unfoldM 'uncons' stdinLn) C.$$ C.print
941-- @
943-- $comparison
944--
945-- Streamly unifies non-determinism, streaming, concurrency and FRP
946-- functionality that is otherwise covered by several disparate packages, and
947-- it does that with a surprisingly concise API. Here is a list of popular and
948-- well-known packages in all these areas:
949--
950-- @
951-- +-----------------+----------------+
952-- | Non-determinism | <https://hackage.haskell.org/package/list-t list-t> |
953-- | +----------------+
954-- | | <https://hackage.haskell.org/package/logict logict> |
955-- +-----------------+----------------+
956-- | Streaming | <https://hackage.haskell.org/package/streaming streaming> |
957-- | +----------------+
958-- | | <https://hackage.haskell.org/package/conduit conduit> |
959-- | +----------------+
960-- | | <https://hackage.haskell.org/package/pipes pipes> |
961-- | +----------------+
962-- | | <https://hackage.haskell.org/package/simple-conduit simple-conduit> |
963-- +-----------------+----------------+
964-- | Concurrency | <https://hackage.haskell.org/package/async async> |
965-- | +----------------+
966-- | | <https://hackage.haskell.org/package/transient transient> |
967-- +-----------------+----------------+
968-- | FRP | <https://hackage.haskell.org/package/Yampa Yampa> |
969-- | +----------------+
970-- | | <https://hackage.haskell.org/package/dunai dunai> |
971-- | +----------------+
972-- | | <https://hackage.haskell.org/package/reflex reflex> |
973-- +-----------------+----------------+
974-- @
975--
976-- Streamly covers all the functionality provided by both the non-determinism
977-- packages listed above and provides better performance in comparison to
978-- those. In fact, at the core streamly is a list transformer but it naturally
979-- integrates the concurrency dimension to the basic list transformer
980-- functionality.
981--
982-- When it comes to streaming, in terms of core concepts, @simple-conduit@ is
983-- the package that is closest to streamly if we set aside the concurrency
984-- dimension, both are streaming packages with list transformer like monad
985-- composition. However, in terms of API it is more like the @streaming@
986-- package. Streamly can be used to achieve more or less the functionality
987-- provided by any of the streaming packages listed above. The types and API of
988-- streamly are much simpler in comparison to conduit and pipes. It is more or
989-- less like the standard Haskell list APIs.
990--
991-- When it comes to concurrency, streamly can do everything that the @async@
992-- package can do and more. async provides applicative concurrency whereas
993-- streamly provides both applicative and monadic concurrency. The 'ZipAsync'
994-- type behaves like the applicative instance of async. This work was
995-- originally inspired by the concurrency implementation in @transient@ though
996-- it has no resemblence with that. Streamly provides concurrency as transient
997-- does but in a sort of dual manner, it can lazily stream the output. In
998-- comparison to transient streamly has a first class streaming interface and
999-- is a monad transformer that can be used universally in any Haskell monad
1000-- transformer stack.
1002-- The non-determinism, concurrency and streaming combination make streamly a
1003-- strong FRP capable library as well. FRP is fundamentally stream of events
1004-- that can be processed concurrently. The example in this tutorial as well as
1005-- the "Streamly.Examples.CirclingSquare" example from Yampa demonstrate the
1006-- basic FRP capability of streamly. In core concepts streamly is strikingly
1007-- similar to @dunai@. dunai was designed from a FRP perspective and streamly
1008-- wa original designed from a concurrency perspective. However, both have
1009-- similarity at the core.