@@ -6,41 +6,27 @@
6-- License : BSD3
6-- License : BSD3
7-- Maintainer : harendra.kumar@gmail.com
7-- Maintainer : harendra.kumar@gmail.com
9−-- Streamly combines streaming, logic programming, concurrency and reactive
9+-- Streamly, short for stream concurrently, combines the essence of streaming
10−-- programming under a single abstraction with a concise API, giving
10+-- and concurrency in functional programming. You can write concurrent as well
11−-- programmers a simple yet powerful tool for wide variety of applications.
11+-- as non-concurrent applications with streamly. Streaming enables writing
12−--
12+-- modular, composable and scalable applications with ease and concurrency
13−-- The 'AsyncT' monad operates on streams. Multiple streams of the same type
13+-- allows you to make them scale and perform well.
14−-- can be summed using different sum style operators. For example, '<>' appends
14+-- Streamly enables writing concurrent applications without being aware of
15−-- streams serially, '<=>' interleaves streams, '<|' combines streams in
16−-- parallel and '<|>' combines streams in a fairly parallel manner. Fold
17−-- utilities are provided to choose a fold style and an operator to fold a
18−-- container in a desired manner producing an AsyncT stream.
20−-- Streams of the same or different types can be zipped or transformed using
21−-- applicative or monadic composition. The monadic composition is exactly the
22−-- same as a list transformer except that it can be concurrent. It can be
23−-- likened to nested loops in the imperative programming parlance. In addition
24−-- to the regular monadic bind operator '>>=', other operators are provided to
25−-- compose streams in alternate manners. For example, '>->' interleaves the
26−-- iterations of a loop, '>>|' runs iterations in parallel and '>|>' runs all
27−-- iterations in fairly parallel manner.
29−-- With streamly you can write concurrent and streaming applications without
30−-- being aware of threads or synchronization. No explicit thread control is
15+-- threads or synchronization. No explicit thread control is needed, where
31−-- needed, where applicable the concurrency rate is automatically controlled
16+-- applicable the concurrency rate is automatically controlled based on the
32−-- based on the demand by the consumer. However, combinators are provided to
17+-- demand by the consumer. However, combinators are provided to fine tune the
33−-- fine tune the concurrency control. Streaming and concurrency allows
18+-- concurrency control.
34−-- expressing reactive applications conveniently. See 'Streamly.Examples' for a
19+-- Streaming and concurrency together enable expressing reactive applications
35−-- simple SDL based FRP example.
20+-- conveniently. See 'Streamly.Examples' for a simple SDL based FRP example.
22+-- In this tutorial we will go over the basic concepts and how to use the
37−-- For examples and other ways to use the library see 'Streamly.Examples' as
23+-- library. For examples and other ways to use the library see
38−-- well.
24+-- the module @Streamly.Examples@ as well.
40module Streamly.Tutorial
26module Streamly.Tutorial
42− -- * AsyncT Monad Transformer
28+ -- * Streams
43− -- $transformer
29+ -- $streams
45 -- ** Generating Streams
31 -- ** Generating Streams
46 -- $generating
32 -- $generating
@@ -51,16 +37,16 @@ module Streamly.Tutorial
51 -- * Semigroup Style Composition
37 -- * Semigroup Style Composition
52 -- $semigroup
38 -- $semigroup
54− -- ** Serial composition (Append)
40+ -- ** Serial composition ('<>')
55 -- $serial
41 -- $serial
57− -- ** Interleaved composition
43+ -- ** Interleaved composition ('<=>')
58 -- $interleaved
44 -- $interleaved
60− -- ** Parallel composition
46+ -- ** Async composition ('<|')
61 -- $parallel
47 -- $parallel
63− -- ** Fairly Parallel composition
49+ -- ** Parallel composition ('<|>')
64 -- $fairParallel
50 -- $fairParallel
66 -- ** Custom composition
52 -- ** Custom composition
@@ -81,6 +67,12 @@ module Streamly.Tutorial
81 -- ** Monad
67 -- ** Monad
70+ -- * Alternate Monadic Compositions
71+ -- $monadicAlternatives
73+ -- ** Serial Iterations
84 -- ** Interleaved Iterations
76 -- ** Interleaved Iterations
85 -- $interleavedNesting
77 -- $interleavedNesting
@@ -116,7 +108,7 @@ module Streamly.Tutorial
119−import Streamly.Core
111+import Streamly
120import Streamly.Prelude
112import Streamly.Prelude
121-- import Streamly.Examples
113-- import Streamly.Examples
122import Data.Semigroup
114import Data.Semigroup
@@ -125,108 +117,124 @@ import Control.Monad
125import Control.Monad.IO.Class (MonadIO(..))
117import Control.Monad.IO.Class (MonadIO(..))
126import Control.Monad.Trans.Class (MonadTrans (lift))
118import Control.Monad.Trans.Class (MonadTrans (lift))
128−-- $transformer
120+-- $streams
122+-- Streamly provides many different stream types depending on the desired
123+-- composition style. The simplest type is 'StreamT'. 'StreamT' is a monad
130−-- 'AsyncT' is a monad transformer, the type 'AsyncT m a' represents a stream
124+-- transformer, the type @StreamT m a@ represents a stream of values of type
131−-- of values of type 'a' in the underlying monad 'm'. In this section we
125+-- 'a' in some underlying monad 'm'. For example, @StreamT IO Int@ is a stream
132−-- describe how to create new values of type 'AsyncT' in the first place and
126+-- of 'Int' in 'IO' monad.
133−-- how to eliminate or run a composed 'AsyncT' computation. Later sections
134−-- discuss how to compose existing AsyncT streams or computations to create
135−-- composite AsyncT streams.
137-- $generating
128-- $generating
139-- Pure values can be placed into the stream type using 'return' or 'pure'.
130-- Pure values can be placed into the stream type using 'return' or 'pure'.
140-- Effects in the IO monad can be lifted to the stream type using the 'liftIO'
131-- Effects in the IO monad can be lifted to the stream type using the 'liftIO'
141-- combinator. In a transformer stack we can lift actions from the lower monad
132-- combinator. In a transformer stack we can lift actions from the lower monad
142−-- using the 'lift' combinator.
133+-- using the 'lift' combinator. Some examples of streams with a single element:
145−-- return 1
136+-- return 1 :: StreamT IO Int
148−-- liftIO $ putStrLn "Hello world!"
139+-- liftIO $ putStrLn "Hello world!" :: StreamT IO ()
142+-- We can combine streams using '<>' to create streams of many elements:
145+-- return 1 <> return 2 <> return 3 :: StreamT IO Int
151-- $eliminating
148-- $eliminating
153−-- 'runStreaming' runs composed actions in a streaming monad, lowering the type
150+-- 'runStreamT' runs a composed 'StreamT' computation, lowering the type into
154−-- into the underlying monad and discarding the values generated by the stream;
151+-- the underlying monad and discarding the result stream:
156+-- main = runStreamT $ liftIO $ putStrLn "Hello world!"
155−-- 'toList' collects the resulting stream in a list. 'uncons' can be used
159+-- 'toList' runs a stream computation and collects the result stream in a list
156−-- to deconstruct a stream into head and tail. Before you run a stream you need
160+-- in the underlying monad. 'toList' is a polymorphic function that works on
157−-- to express the type of the stream by using one of the streaming type
161+-- multiple stream types belonging to the class 'Streaming'. Therefore, before
162+-- you run a stream you need to tell how you want to interpret the stream by
158−-- combinators ('serially', 'asyncly', 'parallely' etc.).
163+-- using one of the stream type combinators ('serially', 'asyncly', 'parallely'
164+-- etc.). The combinator 'serially' is equivalent to annotating the type as @::
161-- import Streamly
168-- import Streamly
163-- main = do
170-- main = do
164−-- runStreaming $ serially $ liftIO $ putStrLn "Hello world!"
165-- xs \<- toList $ serially $ return 1 <> return 2
171-- xs \<- toList $ serially $ return 1 <> return 2
166-- print xs
172-- print xs
175+-- For other ways to eliminate a stream see the module @Streamly.Prelude@.
169-- $semigroup
177-- $semigroup
170−-- AsyncT streams of the same type can be combined into a composite stream in
178+-- Streams of the same type can be combined into a composite stream in many
171−-- many different ways using semigroup style binary composition operators
179+-- different ways using one of the semigroup style binary composition operators
172−-- ('<>', '<=>', '<|', '<|>', 'mplus').
180+-- i.e. '<>', '<=>', '<|', '<|>', 'mplus'. These operators work on all stream
173−-- We will use the following utility functions to illustrate these operators in
181+-- types ('StreamT', 'AsyncT' etc.) uniformly.
174−-- the following sections. The @traced@ function prints the 'threadId' of the
182+--
175−-- thread in which the computation runs and the @timer@ function adds a delay
183+-- To illustrate the concurrent aspects, we will use the following @delay@
176−-- specified by the argument in seconds and then prints the argument.
184+-- function to introduce a delay specified in seconds.
179-- import Streamly
187-- import Streamly
180-- import Control.Concurrent
188-- import Control.Concurrent
182−-- traced m = liftIO (myThreadId >>= print) >> m
184−-- timer n = liftIO $ do
190+-- delay n = liftIO $ do
185-- threadDelay (n * 1000000)
191-- threadDelay (n * 1000000)
186-- tid \<- myThreadId
192-- tid \<- myThreadId
187−-- putStrLn (show tid ++ ": Timer " ++ show n)
193+-- putStrLn (show tid ++ ": Delay " ++ show n)
190-- $serial
196-- $serial
191−-- The '<>' operator composes multiple AsyncT streams in series i.e. the next
197+-- As we have already seen, the '<>' operator composes multiple streams in
192−-- event happens after the previous one is completed. The following example
198+-- series i.e. the next element is yielded only after the previous one has been
199+-- processed completely. The following example prints the sequence 3, 2, 1 and
193−-- prints the sequence 3, 2, 1 and takes a total of 6 seconds:
200+-- takes a total of 6 seconds because everything is serial:
196−-- main = runStreaming $ serially $ timer 3 <> timer 2 <> timer 1
203+-- main = runStreamT $ delay 3 <> delay 2 <> delay 1
199−-- ThreadId 36: Timer 3
206+-- ThreadId 36: Delay 3
200−-- ThreadId 36: Timer 2
207+-- ThreadId 36: Delay 2
201−-- ThreadId 36: Timer 1
208+-- ThreadId 36: Delay 1
204-- $interleaved
211-- $interleaved
205−-- The '<=>' operator interleaves the two computations i.e. it yields one item
212+-- The '<=>' operator interleaves the two computations i.e. it yields one
206−-- from the first stream and then one item from the second stream. The
213+-- element from the first stream and then one element from the second stream,
207−-- following example should print the sequence 1, 3, 2, 4:
214+-- and so on. The following example prints the sequence 1, 3, 2, 4 and takes a
215+-- total of 10 seconds because everything is serial:
210−-- main = runStreaming $ serially $ (timer 1 <> timer 2) \<=> (timer 3 <> timer 4)
218+-- main = runStreamT $ (delay 1 <> delay 2) \<=> (delay 3 <> delay 4)
213−-- ThreadId 36: Timer 1
221+-- ThreadId 36: Delay 1
214−-- ThreadId 36: Timer 3
222+-- ThreadId 36: Delay 3
215−-- ThreadId 36: Timer 2
223+-- ThreadId 36: Delay 2
216−-- ThreadId 36: Timer 4
224+-- ThreadId 36: Delay 4
219-- $parallel
227-- $parallel
221−-- The '<|' operator runs computations in parallel, preferring the first
229+-- The '<|' operator can run computations concurrently, preferring the first
222−-- computation over the second. The second computation is run in parallel with
230+-- computation over the second. The second computation is run concurrently with
223-- the first only if the first computation is not producing enough output to
231-- the first only if the first computation is not producing enough output to
224-- keep the consumer busy otherwise the second computation is run serially
232-- keep the consumer busy otherwise the second computation is run serially
225−-- after the first. We maintain a pool of minimum number of threads, for
233+-- after the previous one. The number of concurrent threads is adapted
226−-- running the computations, that is big enough to match the pull rate of the
227−-- consumer of the stream.
234+-- dynamically based on the pull rate of the consumer of the stream.
228-- Note that the left bias of the operator '<|' is suggested by its shape.
236-- Note that the left bias of the operator '<|' is suggested by its shape.
229−-- It is also half cut '<|>' which is fairly parallel in contrast.
237+-- It is also an unbalanced version of the fairly parallel operator '<|>'.
231-- The following example runs all the parallel computations in a single thread
239-- The following example runs all the parallel computations in a single thread
232-- one after another, because none of them blocks. Note that if the consumer
240-- one after another, because none of them blocks. Note that if the consumer
@@ -234,7 +242,7 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
234-- computation to keep up even if none of them blocks:
242-- computation to keep up even if none of them blocks:
237−-- main = runStreaming $ serially $ traced (sqrt 9) <| traced (sqrt 16) <| traced (sqrt 25)
245+-- main = runStreamT $ traced (sqrt 9) <| traced (sqrt 16) <| traced (sqrt 25)
240-- ThreadId 40
248-- ThreadId 40
@@ -246,18 +254,19 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
246-- next one in a separate thread and so on:
254-- next one in a separate thread and so on:
249−-- main = runStreaming $ serially $ timer 3 <| timer 2 <| timer 1
257+-- main = runStreamT $ delay 3 <| delay 2 <| delay 1
252−-- ThreadId 42: Timer 1
260+-- ThreadId 42: Delay 1
253−-- ThreadId 41: Timer 2
261+-- ThreadId 41: Delay 2
254−-- ThreadId 40: Timer 3
262+-- ThreadId 40: Delay 3
257−-- When we have a hierarchy of computations this operator follows DFS style:
265+-- When we have a tree of computations composed using this operator, the tree
266+-- is traversed in DFS style because it always prefers the left computation:
260−-- main = runStreaming $ serially $ (p 1 <| p 2) <| (p 3 <| p 4)
269+-- main = runStreamT $ (p 1 <| p 2) <| (p 3 <| p 4)
261-- where p = liftIO . print
270-- where p = liftIO . print
@@ -267,15 +276,15 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
270−-- Note that since this operator is left biased it should not be used when the
279+-- Note that since this operator is left biased it cannot be used when the
271−-- computations have timers that are relative to each other because all
280+-- composed computations have timers that are relative to each other because
272−-- computations may not be started at the same time and therefore timers in all
281+-- all computations may not be started at the same time and therefore timers in
273−-- of them will not start at the same time. When relative timing among all
282+-- all of them may not start at the same time. When relative timing among all
274-- computations is important or when we need to start all computations at once
283-- computations is important or when we need to start all computations at once
275−-- for some reason use '<|>' instead. However, '<|' is useful in situations
284+-- for some reason '<|>' must be used instead. However, '<|' is useful in
276−-- when we want to optimally utilize the resources and we know that the
285+-- situations when we want to optimally utilize the resources and we know that
277−-- computations can run in parallel but we do not care if they actually run in
286+-- the computations can run in parallel but we do not care if they actually run
278−-- parallel or not, that decision is left to the scheduler. Also, note that
287+-- in parallel or not, that decision is left to the scheduler. Also, note that
279-- this operator can be used to fold infinite containers in contrast to '<|>',
288-- this operator can be used to fold infinite containers in contrast to '<|>',
280-- because it does not require us to run all of them at the same time.
289-- because it does not require us to run all of them at the same time.
@@ -283,9 +292,9 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
284-- The 'Alternative' composition operator '<|>', like '<|', runs the composed
293-- The 'Alternative' composition operator '<|>', like '<|', runs the composed
285-- computations in parallel. However, unlike '<|' it runs all of the
294-- computations in parallel. However, unlike '<|' it runs all of the
286−-- computations in fairly parallel manner using a round robin scheduling. Note
295+-- computations in fairly parallel manner using a round robin scheduling
287−-- that this should not be used on infinite containers, as it will lead to an
296+-- mechanism. Note that this should not be used on infinite containers, as it
288−-- infinite scheduling queue.
297+-- will lead to an infinite sized scheduling queue.
290-- The following example sends a query to three search engines and prints the
299-- The following example sends a query to three search engines and prints the
291-- name of the search engine as a response arrives:
300-- name of the search engine as a response arrives:
@@ -294,7 +303,7 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
294-- import Streamly
303-- import Streamly
295-- import Network.HTTP.Simple
304-- import Network.HTTP.Simple
297−-- main = runStreaming $ serially $ google \<|> bing \<|> duckduckgo
306+-- main = runStreamT $ google \<|> bing \<|> duckduckgo
299-- google = get "https://www.google.com/search?q=haskell"
308-- google = get "https://www.google.com/search?q=haskell"
300-- bing = get "https://www.bing.com/search?q=haskell"
309-- bing = get "https://www.bing.com/search?q=haskell"
@@ -305,22 +314,24 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
305-- $custom
314-- $custom
307-- The 'async' API can be used to create references to asynchronously running
316-- The 'async' API can be used to create references to asynchronously running
308−-- 'AsyncT' computations. We can then use 'uncons' to explore the
317+-- stream computations. We can then use 'uncons' to explore the streams
309−-- streams arbitrarily and then recompose them to create a new 'AsyncT' stream.
318+-- arbitrarily and then recompose individual elements to create a new stream.
310−-- This way we can dynamically decide which stream to explore at any given time.
319+-- This way we can dynamically decide which stream to explore at any given
311−-- Take an example of a merge sort of two sorted streams. We need to keep
320+-- time. Take an example of a merge sort of two sorted streams. We need to
312−-- consuming items from the stream which has the lowest item in the sort order.
321+-- keep consuming items from the stream which has the lowest item in the sort
313−-- This can be achieved using async references to streams. See
322+-- order. This can be achieved using async references to streams. See
314-- 'Streamly.Examples.MergeSortedStreams'.
323-- 'Streamly.Examples.MergeSortedStreams'.
316-- $monoid
325-- $monoid
318−-- Containers can be folded using a suitable identity ('mempty',
327+-- Each of the semigroup compositions described has an identity that can be
319−-- 'empty', 'mzero') and one of the binary operators mentioned earlier. Some
328+-- used to fold a possibly empty container. An empty stream is represented by
329+-- 'nil' which can be represented in various standard forms as 'mempty',
330+-- 'empty' or 'mzero'.
320−-- fold utilities are provided by the library for convenience:
331+-- Some fold utilities are also provided by the library for convenience:
322−-- * 'foldWith' folds a 'Foldable' container of AsyncT computations using the
333+-- * 'foldWith' folds a 'Foldable' container of stream computations using the
323−-- given function.
334+-- given composition operator.
324-- * 'foldMapWith' folds like foldWith but also maps a function before folding.
335-- * 'foldMapWith' folds like foldWith but also maps a function before folding.
325-- * 'forEachWith' is like foldMapwith but the container argument comes before
336-- * 'forEachWith' is like foldMapwith but the container argument comes before
326-- the function argument.
337-- the function argument.
@@ -329,7 +340,7 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
329-- import Streamly
340-- import Streamly
331-- main = do
342-- main = do
332−-- runStreaming $ serially $ liftIO $ putStrLn "Hello world!"
343+-- runStreaming . serially $ liftIO $ putStrLn "Hello world!"
333-- xs \<- toList . serially $ return 1 <> return 2; print xs
344-- xs \<- toList . serially $ return 1 <> return 2; print xs
334-- xs \<- toList . serially $ foldWith (<>) (map return [1..10]); print xs
345-- xs \<- toList . serially $ foldWith (<>) (map return [1..10]); print xs
335-- xs \<- toList . serially $ foldMapWith (<>) return [1..10]; print xs
346-- xs \<- toList . serially $ foldMapWith (<>) return [1..10]; print xs
@@ -338,40 +349,40 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
339-- $transforming
350-- $transforming
341−-- The previous section discussed ways to compose streams without any
352+-- The previous section discussed ways to merge the elements of two streams
342−-- transformation using sum style operators. In this section we will explore
353+-- without doing any transformation on them. In this section we will explore
343−-- how to transform streams using 'Functor', 'Applicative' or 'Monad'
354+-- how to transform streams using 'Functor', 'Applicative' or 'Monad' style
344−-- style compositions. The applicative and monad composition of 'AsyncT' is
355+-- compositions. The applicative and monad composition of all 'Streaming' types
345−-- exactly the same as a list transformer.
356+-- behave exactly the same way as a list transformer. For simplicity of
346−-- For simplicity of illustration we are using streams of
357+-- illustration we are using streams of pure values in the following examples.
347−-- pure values in the following examples. However, the real application of
358+-- However, the real application of streams arises when these streams are
348−-- AsyncT arises when these streams are generated using monadic actions. In the
359+-- generated using monadic actions. In the pure stream cases 'StreamT' is
349−-- pure stream cases AsyncT is equivalent to the list monad.
360+-- equivalent to the list monad.
351-- $functor
362-- $functor
353−-- The simplest way to transform a stream is by mapping a function on
364+-- The simplest transformation on a stream is mapping a function on
354-- all elements of the stream using 'fmap'.
365-- all elements of the stream using 'fmap'.
357-- import Streamly
368-- import Streamly
359-- main = do
370-- main = do
360−-- let nums = foldMapWith (<>) return [1..10]
371+-- let nums = each [1..10]
361-- let strings = fmap show nums
372-- let strings = fmap show nums
362-- (toList $ serially $ fmap putStrLn strings) >>= sequence
373-- (toList $ serially $ fmap putStrLn strings) >>= sequence
365-- $applicative
376-- $applicative
367−-- You can compose two or more streams using a function in 'Applicative' manner
368−-- as well. In functional programmer's parlance the applicative implements
378+-- In functional programmer's parlance the 'Applicative' instance of
369−-- non-determinism, exploring all possible combination of choices from both the
379+-- 'Streaming' types implement non-determinism, exploring all possible
380+-- combination of choices from both the streams. From an imperative
370−-- streams. From an imperative programmer's point of view the applicative
381+-- programmer's point of view the applicative behaves like nested loops i.e.
371−-- behaves like nested loops i.e. for each element in the first argument and
382+-- for each element in the first argument and for each element in the second
372−-- for each element in the second argument apply the function. Note that the
383+-- argument apply the body of the loop. Note that this behavior of applicative
373−-- behavior of applicative instance of 'AsyncT' is exactly the same as that of
384+-- instance of all 'Streaming' types is exactly the same as that of a list
374−-- a list transformer.
385+-- transformer.
376-- The following example prints all combinations of size, color and shape:
387-- The following example prints all combinations of size, color and shape:
@@ -380,18 +391,17 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
383−-- toStream xs = foldMapWith (<>) return xs
384−-- sizes = toStream [1, 2, 3]
394+-- sizes = each [1, 2, 3]
385−-- colors = toStream ["red", "green", "blue"]
395+-- colors = each ["red", "green", "blue"]
386−-- shapes = toStream ["triangle", "square", "circle"]
396+-- shapes = each ["triangle", "square", "circle"]
391−-- The monadic composition is similar to Applicative composition and just like
401+-- The monadic composition of 'Streaming' types is similar to 'Applicative'
402+-- composition and just the same as that of list transformer. It is like nested
392−-- a list transformer. It is like nested loops in the imperative parlance. The
403+-- loops in the imperative parlance. The monadic continuation is executed for
393−-- monadic continuation is performed for each element in a stream.
404+-- each element in a stream.
395-- For example the following program repeatedly reads strings from standard
405-- For example the following program repeatedly reads strings from standard
396-- input and echoes them to the standard output. This is like one infinite
406-- input and echoes them to the standard output. This is like one infinite
@@ -400,17 +410,28 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
400-- import Streamly
410-- import Streamly
401-- import Data.Semigroup (cycle1)
411-- import Data.Semigroup (cycle1)
403−-- main = runStreaming $ serially $ do
413+-- main = runStreamT $ do
404-- str <- cycle1 (liftIO getLine)
414-- str <- cycle1 (liftIO getLine)
405-- liftIO $ putStrLn str
415-- liftIO $ putStrLn str
418+-- $monadicAlternatives
420+-- Just like we saw multiple non-concurrent and concurrent variants of sum
421+-- style compositions earlier, monadic composition also has multiple variants
422+-- each of which exactly corresponds to one of the sum style composition
408−-- This example is equivalent to three nested loops, the first one for sizes,
425+-- We will use the following polymorphic code which is agnostic of a specific
409−-- the second one for colors and the third one for shapes. It prints all
426+-- 'Streaming' type to illustrate various variants of monadic stream
410−-- combinations of size, color and shape.
427+-- composition. This code can be interpreted in many different ways depending
428+-- on the actual type used.
413−-- main = runStreaming $ serially $ do
431+-- import Streamly
433+-- composed :: Streaming t => t m a
414-- sz <- sizes
435-- sz <- sizes
415-- cl <- colors
436-- cl <- colors
416-- sh <- shapes
437-- sh <- shapes
@@ -418,30 +439,28 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
421−-- toStream xs = foldMapWith (<>) return xs
422−-- sizes = toStream [1, 2, 3]
442+-- sizes = each [1, 2, 3]
423−-- colors = toStream ["red", "green", "blue"]
443+-- colors = each ["red", "green", "blue"]
424−-- shapes = toStream ["triangle", "square", "circle"]
444+-- shapes = each ["triangle", "square", "circle"]
428−-- $interleavedNesting
447+-- $regularSerial
430−-- @
449+-- When we interpret the code as 'StreamT' we get the regular list transformer
431−-- import Streamly
450+-- like serial composition. The following is equivalent to three nested loops,
451+-- the first one for sizes, the second one for colors and the third one for
452+-- shapes. For each size we iterate each color and for each color we iterate
433−-- main = runStreaming $ interleaved $
456+-- main = runStreaming $ serially $ composed
434−-- sz <- sizes
457+-- @
437−-- liftIO $ putStrLn $ show (sz, cl, sh)
460+-- $interleavedNesting
441−-- toStream xs = foldMapWith (<>) return xs
462+-- @
442−-- sizes = toStream [1, 2, 3]
463+-- main = runStreaming $ interleaved $ composed
443−-- colors = toStream ["red", "green", "blue"]
444−-- shapes = toStream ["triangle", "square", "circle"]
447-- $concurrentNesting
466-- $concurrentNesting