on master help sign in
Some updates to tutorial
commit19ac788a9fe6b102f4a98899a30f128e8c0da83d authorHarendra Kumar <harendra.kumar@gmail.com> dateWed 22 Nov 2017, 23:00 (9 years ago) parent3a5af7e
2 files changed+188−169
src/Streamly/Tutorial.hs+187−168 streamly.cabal+1−1
src/Streamly/Tutorial.hs+187−168
@@ -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
streamly.cabal+1−1
@@ -6,7 +6,7 @@ description: Streamly unifies concurrency and streaming in a single monad
66 streams, a monadic product composition as well as the standard pipelined
77 composition provided by streaming libraries. A natural extension of regular
88 monadic composition to streaming and concurrency makes it intuitive and
9− concise with almost unviversal application. You can write concurrent or
9+ concise with almost universal application. You can write concurrent or
1010 non-concurrent applications using simple IO, logic programming, streaming IO
1111 or reactive programming (FRP) using the same API. You can also think about it
1212 as representing concurrent and composable state machines in imperative terms.