@@ -6,41 +6,27 @@
66-- License : BSD3
77-- Maintainer : harendra.kumar@gmail.com
88--
9−-- Streamly combines streaming, logic programming, concurrency and reactive
10−-- programming under a single abstraction with a concise API, giving
11−-- programmers a simple yet powerful tool for wide variety of applications.
12−--
13−-- The 'AsyncT' monad operates on streams. Multiple streams of the same type
14−-- can be summed using different sum style operators. For example, '<>' appends
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.
19−--
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.
28−--
29−-- With streamly you can write concurrent and streaming applications without
30−-- being aware of threads or synchronization. No explicit thread control is
31−-- needed, where applicable the concurrency rate is automatically controlled
32−-- based on the demand by the consumer. However, combinators are provided to
33−-- fine tune the concurrency control. Streaming and concurrency allows
34−-- expressing reactive applications conveniently. See 'Streamly.Examples' for a
35−-- simple SDL based FRP example.
36−--
37−-- For examples and other ways to use the library see 'Streamly.Examples' as
38−-- well.
39−--
9+-- Streamly, short for stream concurrently, combines the essence of streaming
10+-- and concurrency in functional programming. You can write concurrent as well
11+-- as non-concurrent applications with streamly. Streaming enables writing
12+-- modular, composable and scalable applications with ease and concurrency
13+-- allows you to make them scale and perform well.
14+-- Streamly enables writing concurrent applications without being aware of
15+-- threads or synchronization. No explicit thread control is needed, where
16+-- applicable the concurrency rate is automatically controlled based on the
17+-- demand by the consumer. However, combinators are provided to fine tune the
18+-- concurrency control.
19+-- Streaming and concurrency together enable expressing reactive applications
20+-- conveniently. See 'Streamly.Examples' for a simple SDL based FRP example.
21+--
22+-- In this tutorial we will go over the basic concepts and how to use the
23+-- library. For examples and other ways to use the library see
24+-- the module @Streamly.Examples@ as well.
25+
4026module Streamly.Tutorial
4127 (
42− -- * AsyncT Monad Transformer
43− -- $transformer
28+ -- * Streams
29+ -- $streams
4430
4531 -- ** Generating Streams
4632 -- $generating
@@ -51,16 +37,16 @@ module Streamly.Tutorial
5137 -- * Semigroup Style Composition
5238 -- $semigroup
5339
54− -- ** Serial composition (Append)
40+ -- ** Serial composition ('<>')
5541 -- $serial
5642
57− -- ** Interleaved composition
43+ -- ** Interleaved composition ('<=>')
5844 -- $interleaved
5945
60− -- ** Parallel composition
46+ -- ** Async composition ('<|')
6147 -- $parallel
6248
63− -- ** Fairly Parallel composition
49+ -- ** Parallel composition ('<|>')
6450 -- $fairParallel
6551
6652 -- ** Custom composition
@@ -81,6 +67,12 @@ module Streamly.Tutorial
8167 -- ** Monad
8268 -- $listt
8369
70+ -- * Alternate Monadic Compositions
71+ -- $monadicAlternatives
72+
73+ -- ** Serial Iterations
74+ -- $regularSerial
75+
8476 -- ** Interleaved Iterations
8577 -- $interleavedNesting
8678
@@ -116,7 +108,7 @@ module Streamly.Tutorial
116108 )
117109where
118110
119−import Streamly.Core
111+import Streamly
120112import Streamly.Prelude
121113-- import Streamly.Examples
122114import Data.Semigroup
@@ -125,108 +117,124 @@ import Control.Monad
125117import Control.Monad.IO.Class (MonadIO(..))
126118import Control.Monad.Trans.Class (MonadTrans (lift))
127119
128−-- $transformer
120+-- $streams
129121--
130−-- 'AsyncT' is a monad transformer, the type 'AsyncT m a' represents a stream
131−-- of values of type 'a' in the underlying monad 'm'. In this section we
132−-- describe how to create new values of type 'AsyncT' in the first place and
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.
122+-- Streamly provides many different stream types depending on the desired
123+-- composition style. The simplest type is 'StreamT'. 'StreamT' is a monad
124+-- transformer, the type @StreamT m a@ represents a stream of values of type
125+-- 'a' in some underlying monad 'm'. For example, @StreamT IO Int@ is a stream
126+-- of 'Int' in 'IO' monad.
136127
137128-- $generating
138129--
139130-- Pure values can be placed into the stream type using 'return' or 'pure'.
140131-- Effects in the IO monad can be lifted to the stream type using the 'liftIO'
141132-- 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:
143134--
144135-- @
145−-- return 1
136+-- return 1 :: StreamT IO Int
146137-- @
147138-- @
148−-- liftIO $ putStrLn "Hello world!"
139+-- liftIO $ putStrLn "Hello world!" :: StreamT IO ()
140+-- @
141+--
142+-- We can combine streams using '<>' to create streams of many elements:
143+--
144+-- @
145+-- return 1 <> return 2 <> return 3 :: StreamT IO Int
149146-- @
150147
151148-- $eliminating
152149--
153−-- 'runStreaming' runs composed actions in a streaming monad, lowering the type
154−-- into the underlying monad and discarding the values generated by the stream;
155−-- 'toList' collects the resulting stream in a list. 'uncons' can be used
156−-- to deconstruct a stream into head and tail. Before you run a stream you need
157−-- to express the type of the stream by using one of the streaming type
158−-- combinators ('serially', 'asyncly', 'parallely' etc.).
150+-- 'runStreamT' runs a composed 'StreamT' computation, lowering the type into
151+-- the underlying monad and discarding the result stream:
152+--
153+-- @
154+-- import Streamly
155+--
156+-- main = runStreamT $ liftIO $ putStrLn "Hello world!"
157+-- @
158+--
159+-- 'toList' runs a stream computation and collects the result stream in a list
160+-- in the underlying monad. 'toList' is a polymorphic function that works on
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
163+-- using one of the stream type combinators ('serially', 'asyncly', 'parallely'
164+-- etc.). The combinator 'serially' is equivalent to annotating the type as @::
165+-- StreamT@.
159166--
160167-- @
161168-- import Streamly
162169--
163170-- main = do
164−-- runStreaming $ serially $ liftIO $ putStrLn "Hello world!"
165171-- xs \<- toList $ serially $ return 1 <> return 2
166172-- print xs
167173-- @
174+--
175+-- For other ways to eliminate a stream see the module @Streamly.Prelude@.
168176
169177-- $semigroup
170−-- AsyncT streams of the same type can be combined into a composite stream in
171−-- many different ways using semigroup style binary composition operators
172−-- ('<>', '<=>', '<|', '<|>', 'mplus').
173−-- We will use the following utility functions to illustrate these operators in
174−-- the following sections. The @traced@ function prints the 'threadId' of the
175−-- thread in which the computation runs and the @timer@ function adds a delay
176−-- specified by the argument in seconds and then prints the argument.
178+-- Streams of the same type can be combined into a composite stream in many
179+-- different ways using one of the semigroup style binary composition operators
180+-- i.e. '<>', '<=>', '<|', '<|>', 'mplus'. These operators work on all stream
181+-- types ('StreamT', 'AsyncT' etc.) uniformly.
182+--
183+-- To illustrate the concurrent aspects, we will use the following @delay@
184+-- function to introduce a delay specified in seconds.
177185--
178186-- @
179187-- import Streamly
180188-- import Control.Concurrent
181189--
182−-- traced m = liftIO (myThreadId >>= print) >> m
183−--
184−-- timer n = liftIO $ do
190+-- delay n = liftIO $ do
185191-- threadDelay (n * 1000000)
186192-- tid \<- myThreadId
187−-- putStrLn (show tid ++ ": Timer " ++ show n)
193+-- putStrLn (show tid ++ ": Delay " ++ show n)
188194-- @
189195
190196-- $serial
191−-- The '<>' operator composes multiple AsyncT streams in series i.e. the next
192−-- event happens after the previous one is completed. The following example
193−-- prints the sequence 3, 2, 1 and takes a total of 6 seconds:
197+-- As we have already seen, the '<>' operator composes multiple streams in
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
200+-- takes a total of 6 seconds because everything is serial:
194201--
195202-- @
196−-- main = runStreaming $ serially $ timer 3 <> timer 2 <> timer 1
203+-- main = runStreamT $ delay 3 <> delay 2 <> delay 1
197204-- @
198205-- @
199−-- ThreadId 36: Timer 3
200−-- ThreadId 36: Timer 2
201−-- ThreadId 36: Timer 1
206+-- ThreadId 36: Delay 3
207+-- ThreadId 36: Delay 2
208+-- ThreadId 36: Delay 1
202209-- @
203210
204211-- $interleaved
205−-- The '<=>' operator interleaves the two computations i.e. it yields one item
206−-- from the first stream and then one item from the second stream. The
207−-- following example should print the sequence 1, 3, 2, 4:
212+-- The '<=>' operator interleaves the two computations i.e. it yields one
213+-- element from the first stream and then one element from the second stream,
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:
208216--
209217-- @
210−-- main = runStreaming $ serially $ (timer 1 <> timer 2) \<=> (timer 3 <> timer 4)
218+-- main = runStreamT $ (delay 1 <> delay 2) \<=> (delay 3 <> delay 4)
211219-- @
212220-- @
213−-- ThreadId 36: Timer 1
214−-- ThreadId 36: Timer 3
215−-- ThreadId 36: Timer 2
216−-- ThreadId 36: Timer 4
221+-- ThreadId 36: Delay 1
222+-- ThreadId 36: Delay 3
223+-- ThreadId 36: Delay 2
224+-- ThreadId 36: Delay 4
217225-- @
218226
219227-- $parallel
220228--
221−-- The '<|' operator runs computations in parallel, preferring the first
222−-- computation over the second. The second computation is run in parallel with
229+-- The '<|' operator can run computations concurrently, preferring the first
230+-- computation over the second. The second computation is run concurrently with
223231-- the first only if the first computation is not producing enough output to
224232-- 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
226−-- running the computations, that is big enough to match the pull rate of the
227−-- consumer of the stream.
233+-- after the previous one. The number of concurrent threads is adapted
234+-- dynamically based on the pull rate of the consumer of the stream.
235+--
228236-- 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 '<|>'.
230238--
231239-- The following example runs all the parallel computations in a single thread
232240-- one after another, because none of them blocks. Note that if the consumer
@@ -234,7 +242,7 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
234242-- computation to keep up even if none of them blocks:
235243--
236244-- @
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)
238246-- @
239247-- @
240248-- ThreadId 40
@@ -246,18 +254,19 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
246254-- next one in a separate thread and so on:
247255--
248256-- @
249−-- main = runStreaming $ serially $ timer 3 <| timer 2 <| timer 1
257+-- main = runStreamT $ delay 3 <| delay 2 <| delay 1
250258-- @
251259-- @
252−-- ThreadId 42: Timer 1
253−-- ThreadId 41: Timer 2
254−-- ThreadId 40: Timer 3
260+-- ThreadId 42: Delay 1
261+-- ThreadId 41: Delay 2
262+-- ThreadId 40: Delay 3
255263-- @
256264--
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:
258267--
259268-- @
260−-- main = runStreaming $ serially $ (p 1 <| p 2) <| (p 3 <| p 4)
269+-- main = runStreamT $ (p 1 <| p 2) <| (p 3 <| p 4)
261270-- where p = liftIO . print
262271-- @
263272-- @
@@ -267,15 +276,15 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
267276-- 4
268277-- @
269278--
270−-- Note that since this operator is left biased it should not be used when the
271−-- computations have timers that are relative to each other because all
272−-- computations may not be started at the same time and therefore timers in all
273−-- of them will not start at the same time. When relative timing among all
279+-- Note that since this operator is left biased it cannot be used when the
280+-- composed computations have timers that are relative to each other because
281+-- all computations may not be started at the same time and therefore timers in
282+-- all of them may not start at the same time. When relative timing among all
274283-- computations is important or when we need to start all computations at once
275−-- for some reason use '<|>' instead. However, '<|' is useful in situations
276−-- when we want to optimally utilize the resources and we know that the
277−-- computations can run in parallel but we do not care if they actually run in
278−-- parallel or not, that decision is left to the scheduler. Also, note that
284+-- for some reason '<|>' must be used instead. However, '<|' is useful in
285+-- situations when we want to optimally utilize the resources and we know that
286+-- the computations can run in parallel but we do not care if they actually run
287+-- in parallel or not, that decision is left to the scheduler. Also, note that
279288-- this operator can be used to fold infinite containers in contrast to '<|>',
280289-- because it does not require us to run all of them at the same time.
281290
@@ -283,9 +292,9 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
283292--
284293-- The 'Alternative' composition operator '<|>', like '<|', runs the composed
285294-- computations in parallel. However, unlike '<|' it runs all of the
286−-- computations in fairly parallel manner using a round robin scheduling. Note
287−-- that this should not be used on infinite containers, as it will lead to an
288−-- infinite scheduling queue.
295+-- computations in fairly parallel manner using a round robin scheduling
296+-- mechanism. Note that this should not be used on infinite containers, as it
297+-- will lead to an infinite sized scheduling queue.
289298--
290299-- The following example sends a query to three search engines and prints the
291300-- name of the search engine as a response arrives:
@@ -294,7 +303,7 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
294303-- import Streamly
295304-- import Network.HTTP.Simple
296305--
297−-- main = runStreaming $ serially $ google \<|> bing \<|> duckduckgo
306+-- main = runStreamT $ google \<|> bing \<|> duckduckgo
298307-- where
299308-- google = get "https://www.google.com/search?q=haskell"
300309-- bing = get "https://www.bing.com/search?q=haskell"
@@ -305,22 +314,24 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
305314-- $custom
306315--
307316-- The 'async' API can be used to create references to asynchronously running
308−-- 'AsyncT' computations. We can then use 'uncons' to explore the
309−-- streams arbitrarily and then recompose them to create a new 'AsyncT' stream.
310−-- This way we can dynamically decide which stream to explore at any given time.
311−-- Take an example of a merge sort of two sorted streams. We need to keep
312−-- consuming items from the stream which has the lowest item in the sort order.
313−-- This can be achieved using async references to streams. See
317+-- stream computations. We can then use 'uncons' to explore the streams
318+-- arbitrarily and then recompose individual elements to create a new stream.
319+-- This way we can dynamically decide which stream to explore at any given
320+-- time. Take an example of a merge sort of two sorted streams. We need to
321+-- keep consuming items from the stream which has the lowest item in the sort
322+-- order. This can be achieved using async references to streams. See
314323-- 'Streamly.Examples.MergeSortedStreams'.
315324
316325-- $monoid
317326--
318−-- Containers can be folded using a suitable identity ('mempty',
319−-- 'empty', 'mzero') and one of the binary operators mentioned earlier. Some
320−-- fold utilities are provided by the library for convenience:
327+-- Each of the semigroup compositions described has an identity that can be
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'.
331+-- Some fold utilities are also provided by the library for convenience:
321332--
322−-- * 'foldWith' folds a 'Foldable' container of AsyncT computations using the
323−-- given function.
333+-- * 'foldWith' folds a 'Foldable' container of stream computations using the
334+-- given composition operator.
324335-- * 'foldMapWith' folds like foldWith but also maps a function before folding.
325336-- * 'forEachWith' is like foldMapwith but the container argument comes before
326337-- the function argument.
@@ -329,7 +340,7 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
329340-- import Streamly
330341--
331342-- main = do
332−-- runStreaming $ serially $ liftIO $ putStrLn "Hello world!"
343+-- runStreaming . serially $ liftIO $ putStrLn "Hello world!"
333344-- xs \<- toList . serially $ return 1 <> return 2; print xs
334345-- xs \<- toList . serially $ foldWith (<>) (map return [1..10]); print xs
335346-- xs \<- toList . serially $ foldMapWith (<>) return [1..10]; print xs
@@ -338,40 +349,40 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
338349
339350-- $transforming
340351--
341−-- The previous section discussed ways to compose streams without any
342−-- transformation using sum style operators. In this section we will explore
343−-- how to transform streams using 'Functor', 'Applicative' or 'Monad'
344−-- style compositions. The applicative and monad composition of 'AsyncT' is
345−-- exactly the same as a list transformer.
346−-- For simplicity of illustration we are using streams of
347−-- pure values in the following examples. However, the real application of
348−-- AsyncT arises when these streams are generated using monadic actions. In the
349−-- pure stream cases AsyncT is equivalent to the list monad.
352+-- The previous section discussed ways to merge the elements of two streams
353+-- without doing any transformation on them. In this section we will explore
354+-- how to transform streams using 'Functor', 'Applicative' or 'Monad' style
355+-- compositions. The applicative and monad composition of all 'Streaming' types
356+-- behave exactly the same way as a list transformer. For simplicity of
357+-- illustration we are using streams of pure values in the following examples.
358+-- However, the real application of streams arises when these streams are
359+-- generated using monadic actions. In the pure stream cases 'StreamT' is
360+-- equivalent to the list monad.
350361
351362-- $functor
352363--
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
354365-- all elements of the stream using 'fmap'.
355366--
356367-- @
357368-- import Streamly
358369--
359370-- main = do
360−-- let nums = foldMapWith (<>) return [1..10]
371+-- let nums = each [1..10]
361372-- let strings = fmap show nums
362373-- (toList $ serially $ fmap putStrLn strings) >>= sequence
363374-- @
364375
365376-- $applicative
366377--
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
369−-- non-determinism, exploring all possible combination of choices from both the
370−-- streams. From an imperative programmer's point of view the applicative
371−-- behaves like nested loops i.e. for each element in the first argument and
372−-- for each element in the second argument apply the function. Note that the
373−-- behavior of applicative instance of 'AsyncT' is exactly the same as that of
374−-- a list transformer.
378+-- In functional programmer's parlance the 'Applicative' instance of
379+-- 'Streaming' types implement non-determinism, exploring all possible
380+-- combination of choices from both the streams. From an imperative
381+-- programmer's point of view the applicative behaves like nested loops i.e.
382+-- for each element in the first argument and for each element in the second
383+-- argument apply the body of the loop. Note that this behavior of applicative
384+-- instance of all 'Streaming' types is exactly the same as that of a list
385+-- transformer.
375386--
376387-- The following example prints all combinations of size, color and shape:
377388--
@@ -380,18 +391,17 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
380391--
381392-- where
382393--
383−-- toStream xs = foldMapWith (<>) return xs
384−-- sizes = toStream [1, 2, 3]
385−-- colors = toStream ["red", "green", "blue"]
386−-- shapes = toStream ["triangle", "square", "circle"]
394+-- sizes = each [1, 2, 3]
395+-- colors = each ["red", "green", "blue"]
396+-- shapes = each ["triangle", "square", "circle"]
387397-- @
388398
389399-- $listt
390400--
391−-- The monadic composition is similar to Applicative composition and just like
392−-- a list transformer. It is like nested loops in the imperative parlance. The
393−-- monadic continuation is performed for each element in a stream.
394−--
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
403+-- loops in the imperative parlance. The monadic continuation is executed for
404+-- each element in a stream.
395405-- For example the following program repeatedly reads strings from standard
396406-- input and echoes them to the standard output. This is like one infinite
397407-- loop:
@@ -400,17 +410,28 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
400410-- import Streamly
401411-- import Data.Semigroup (cycle1)
402412--
403−-- main = runStreaming $ serially $ do
413+-- main = runStreamT $ do
404414-- str <- cycle1 (liftIO getLine)
405415-- liftIO $ putStrLn str
406416-- @
417+
418+-- $monadicAlternatives
419+--
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
423+-- variant.
407424--
408−-- This example is equivalent to three nested loops, the first one for sizes,
409−-- the second one for colors and the third one for shapes. It prints all
410−-- combinations of size, color and shape.
425+-- We will use the following polymorphic code which is agnostic of a specific
426+-- 'Streaming' type to illustrate various variants of monadic stream
427+-- composition. This code can be interpreted in many different ways depending
428+-- on the actual type used.
411429--
412430-- @
413−-- main = runStreaming $ serially $ do
431+-- import Streamly
432+--
433+-- composed :: Streaming t => t m a
434+-- composed = do
414435-- sz <- sizes
415436-- cl <- colors
416437-- sh <- shapes
@@ -418,30 +439,28 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
418439--
419440-- where
420441--
421−-- toStream xs = foldMapWith (<>) return xs
422−-- sizes = toStream [1, 2, 3]
423−-- colors = toStream ["red", "green", "blue"]
424−-- shapes = toStream ["triangle", "square", "circle"]
442+-- sizes = each [1, 2, 3]
443+-- colors = each ["red", "green", "blue"]
444+-- shapes = each ["triangle", "square", "circle"]
425445-- @
426−--
427446
428−-- $interleavedNesting
447+-- $regularSerial
429448--
430−-- @
431−-- import Streamly
449+-- When we interpret the code as 'StreamT' we get the regular list transformer
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
453+-- each shape.
432454--
433−-- main = runStreaming $ interleaved $
434−-- sz <- sizes
435−-- cl <- colors
436−-- sh <- shapes
437−-- liftIO $ putStrLn $ show (sz, cl, sh)
455+-- @
456+-- main = runStreaming $ serially $ composed
457+-- @
438458--
439−-- where
459+
460+-- $interleavedNesting
440461--
441−-- toStream xs = foldMapWith (<>) return xs
442−-- sizes = toStream [1, 2, 3]
443−-- colors = toStream ["red", "green", "blue"]
444−-- shapes = toStream ["triangle", "square", "circle"]
462+-- @
463+-- main = runStreaming $ interleaved $ composed
445464-- @
446465
447466-- $concurrentNesting