@@ -34,18 +34,21 @@ module Streamly.Tutorial
3434 -- ** Eliminating Streams
3535 -- $eliminating
3636
37+ -- * Folding Streams
38+ -- $folding
39+
3740 -- * Semigroup Style Composition
3841 -- $semigroup
3942
4043 -- ** Serial composition ('<>')
4144 -- $serial
4245
43− -- ** Interleaved composition ('<=>')
44− -- $interleaved
45−
4646 -- ** Async composition ('<|')
4747 -- $parallel
4848
49+ -- ** Interleaved composition ('<=>')
50+ -- $interleaved
51+
4952 -- ** Parallel composition ('<|>')
5053 -- $fairParallel
5154
@@ -58,32 +61,32 @@ module Streamly.Tutorial
5861 -- * Transforming Streams
5962 -- $transforming
6063
61− -- ** Functor
62− -- $functor
63−
64− -- ** Applicative
65− -- $applicative
66−
6764 -- ** Monad
68− -- $listt
69−
70− -- * Alternate Monadic Compositions
71− -- $monadicAlternatives
65+ -- $monad
7266
73− -- ** Serial Iterations
67+ -- *** Serial Iterations ('StreamT')
7468 -- $regularSerial
7569
76− -- ** Interleaved Iterations
77− -- $interleavedNesting
78−
79− -- ** Concurrent Iterations
70+ -- *** Concurrent Iterations ('AsyncT')
8071 -- $concurrentNesting
8172
82− -- ** Fairly Concurrent Iterations
73+ -- *** Interleaved Iterations ('InterleavedT')
74+ -- $interleavedNesting
75+
76+ -- *** Fairly Concurrent Iterations ('ParallelT')
8377 -- $fairlyConcurrentNesting
8478
85− -- * Folding Streams
86− -- $folding
79+ -- *** Exercise
80+ -- $monadExercise
81+
82+ -- ** Applicative
83+ -- $applicative
84+
85+ -- ** Functor
86+ -- $functor
87+
88+ -- * Summary of Compositions
89+ -- $compositionSummary
8790
8891 -- * Zipping Streams
8992 -- $zipping
@@ -194,10 +197,11 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
194197-- @
195198
196199-- $serial
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:
200+--
201+-- We have already seen, the '<>' operator. It composes two streams in series
202+-- i.e. the first stream is completely exhausted and then the second stream is
203+-- processed. The following example prints the sequence 3, 2, 1 and takes a
204+-- total of 6 seconds because everything is serial:
201205--
202206-- @
203207-- main = runStreamT $ delay 3 <> delay 2 <> delay 1
@@ -209,10 +213,10 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
209213-- @
210214
211215-- $interleaved
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:
216+-- The '<=>' operator is serial like '<>' but it interleaves the two streams
217+-- i.e. it yields one element from the first stream and then one element from
218+-- the second stream, and so on. The following example prints the sequence 1,
219+-- 3, 2, 4 and takes a total of 10 seconds because everything is serial:
216220--
217221-- @
218222-- main = runStreamT $ (delay 1 <> delay 2) \<=> (delay 3 <> delay 4)
@@ -223,33 +227,13 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
223227-- ThreadId 36: Delay 2
224228-- ThreadId 36: Delay 4
225229-- @
230+--
231+-- Note that this operator cannot be used to fold infinite containers since it
232+-- requires preserving the state until a stream is finished.
226233
227234-- $parallel
228235--
229−-- The '<|' operator can run computations concurrently, preferring the first
230−-- computation over the second. The second computation is run concurrently with
231−-- the first only if the first computation is not producing enough output to
232−-- keep the consumer busy otherwise the second computation is run serially
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−--
236−-- Note that the left bias of the operator '<|' is suggested by its shape.
237−-- It is also an unbalanced version of the fairly parallel operator '<|>'.
238−--
239−-- The following example runs all the parallel computations in a single thread
240−-- one after another, because none of them blocks. Note that if the consumer
241−-- were faster than the producer this would start new threads for each
242−-- computation to keep up even if none of them blocks:
243−--
244−-- @
245−-- main = runStreamT $ traced (sqrt 9) <| traced (sqrt 16) <| traced (sqrt 25)
246−-- @
247−-- @
248−-- ThreadId 40
249−-- ThreadId 40
250−-- ThreadId 40
251−-- @
252−--
236+-- The '<|' operator can run both computations concurrently, /when needed/.
253237-- In the following example since the first computation blocks we start the
254238-- next one in a separate thread and so on:
255239--
@@ -262,8 +246,10 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
262246-- ThreadId 40: Delay 3
263247-- @
264248--
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:
249+-- This is the concurrent version of the '<>' operator. The computations are
250+-- triggered in the same order as '<>' except that they are concurrent. When
251+-- we have a tree of computations composed using this operator, the tree is
252+-- traversed in DFS style just like '<>'.
267253--
268254-- @
269255-- main = runStreamT $ (p 1 <| p 2) <| (p 3 <| p 4)
@@ -276,28 +262,55 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
276262-- 4
277263-- @
278264--
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
283−-- computations is important or when we need to start all computations at once
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
288−-- this operator can be used to fold infinite containers in contrast to '<|>',
289−-- because it does not require us to run all of them at the same time.
265+-- Concurrency provided by this operator is demand driven. The second
266+-- computation is run concurrently with the first only if the first computation
267+-- is not producing enough output to keep the stream consumer busy otherwise
268+-- the second computation is run serially after the previous one. The number of
269+-- concurrent threads is adapted dynamically based on the pull rate of the
270+-- consumer of the stream.
271+-- As you can see, in the following example the computations are run in a
272+-- single thread one after another, because none of them blocks. However, if
273+-- the thread consuming the stream were faster than the producer then it would
274+-- have started parallel threads for each computation to keep up even if none
275+-- of them blocks:
276+--
277+-- @
278+-- main = runStreamT $ traced (sqrt 9) <| traced (sqrt 16) <| traced (sqrt 25)
279+-- @
280+-- @
281+-- ThreadId 40
282+-- ThreadId 40
283+-- ThreadId 40
284+-- @
285+--
286+-- Since the concurrency provided by this operator is demand driven it cannot
287+-- be used when the composed computations have timers that are relative to each
288+-- other because all computations may not be started at the same time and
289+-- therefore timers in all of them may not start at the same time. When
290+-- relative timing among all computations is important or when we need to start
291+-- all computations at once for some reason '<|>' must be used instead.
292+-- However, '<|' is useful in situations when we want to optimally utilize the
293+-- resources and we know that the computations can run in parallel but we do
294+-- not care if they actually run in parallel or not, that decision is left to
295+-- the scheduler. Also, note that this operator can be used to fold infinite
296+-- containers in contrast to '<|>', because it does not require us to run all
297+-- of them at the same time.
298+--
299+-- The left bias (or the DFS style) of the operator '<|' is suggested by its
300+-- shape. You can also think of this as an unbalanced version of the fairly
301+-- parallel operator '<|>'.
290302
291303-- $fairParallel
292304--
293305-- The 'Alternative' composition operator '<|>', like '<|', runs the composed
294−-- computations in parallel. However, unlike '<|' it runs all of the
306+-- computations concurrently. However, unlike '<|' it runs all of the
295307-- 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.
308+-- mechanism. This can be considered as the concurrent version of the fairly
309+-- interleaved serial operation '<=>'. Note that this cannot be used on
310+-- infinite containers, as it will lead to an infinite sized scheduling queue.
298311--
299−-- The following example sends a query to three search engines and prints the
300−-- name of the search engine as a response arrives:
312+-- The following example sends a query to three search engines in parallel and
313+-- prints the name of the search engine as a response arrives:
301314--
302315-- @
303316-- import Streamly
@@ -335,16 +348,19 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
335348-- * 'foldMapWith' folds like foldWith but also maps a function before folding.
336349-- * 'forEachWith' is like foldMapwith but the container argument comes before
337350-- the function argument.
351+-- * The 'each' primitive from @Streamly.Prelude@ folds a 'Foldable' container
352+-- using the '<>' operator:
353+--
354+-- All of the following are equivalent:
338355--
339356-- @
340357-- import Streamly
341358--
342359-- main = do
343−-- runStreaming . serially $ liftIO $ putStrLn "Hello world!"
344−-- xs \<- toList . serially $ return 1 <> return 2; print xs
345−-- xs \<- toList . serially $ foldWith (<>) (map return [1..10]); print xs
346−-- xs \<- toList . serially $ foldMapWith (<>) return [1..10]; print xs
347−-- xs \<- toList . serially $ forEachWith (<>) [1..10] return; print xs
360+-- toList . serially $ foldWith (<>) (map return [1..10]) >>= print
361+-- toList . serially $ foldMapWith (<>) return [1..10] >>= print
362+-- toList . serially $ forEachWith (<>) [1..10] return >>= print
363+-- toList . serially $ each [1..10] >>= print
348364-- @
349365
350366-- $transforming
@@ -356,76 +372,163 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
356372-- behave exactly the same way as a list transformer. For simplicity of
357373-- illustration we are using streams of pure values in the following examples.
358374-- 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.
375+-- generated using monadic actions.
361376
362−-- $functor
377+-- $monad
363378--
364−-- The simplest transformation on a stream is mapping a function on
365−-- all elements of the stream using 'fmap'.
379+-- In functional programmer's parlance the 'Monad' instance of 'Streaming'
380+-- types implement non-determinism, exploring all possible combination of
381+-- choices from both the streams. From an imperative programmer's point of view
382+-- it behaves like nested loops i.e. for each element in the first stream and
383+-- for each element in the second stream apply the body of the loop. If you are
384+-- familiar with list transformer this behavior is exactly the same as that of
385+-- a list transformer.
386+--
387+-- Just like we saw in sum style compositions earlier, monadic composition also
388+-- has multiple variants each of which exactly corresponds to one of the sum
389+-- style composition variant.
390+
391+-- $regularSerial
392+--
393+-- When we interpret the monadic composition as 'StreamT' we get a standard
394+-- list transformer like serial composition.
366395--
367396-- @
368−-- import Streamly
397+-- main = runStreamT $ do
398+-- x <- each [3,2,1]
399+-- delay x
400+-- @
401+-- @
402+-- ThreadId 30: Delay 3
403+-- ThreadId 30: Delay 2
404+-- ThreadId 30: Delay 1
405+-- @
406+--
407+-- As you can see the code after the @each@ statement is run three times, once
408+-- for each value of @x@. All the three iterations are serial and run in the
409+-- same thread one after another. When compared to imperative programming, this
410+-- can also be viewed as a @for@ loop with three iterations.
411+--
412+-- When multiple streams are composed using this style they nest in a DFS
413+-- manner i.e. nested iterations of an iteration are executed before we proceed
414+-- to the next iteration at higher level. This behaves just like nested @for@
415+-- loops in imperative programming.
369416--
370−-- main = do
371−-- let nums = each [1..10]
372−-- let strings = fmap show nums
373−-- (toList $ serially $ fmap putStrLn strings) >>= sequence
374417-- @
375−
376−-- $applicative
418+-- main = runStreamT $ do
419+-- x <- each [1,2]
420+-- y <- each [3,4]
421+-- liftIO $ putStrLn $ show (x, y)
422+-- @
423+-- @
424+-- (1,3)
425+-- (1,4)
426+-- (2,3)
427+-- (2,4)
428+-- @
377429--
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.
430+-- You will also notice that this is the monadic equivalent of the sum style
431+-- composition using '<>'.
432+
433+-- $concurrentNesting
386434--
387−-- The following example prints all combinations of size, color and shape:
435+-- When we interpret the monadic composition as 'AsyncT' we get a /concurrent/
436+-- list-transformer like composition. Multiple monadic continuations (or loop
437+-- iterations) may be started concurrently. Concurrency is demand driven
438+-- i.e. more concurrent iterations are started only if the previous iterations
439+-- are not able to produce enough output for the consumer of the output stream.
440+-- This is the concurrent version of 'StreamT'.
388441--
389442-- @
390−-- main = (toList . serially $ (,,) \<$> sizes \<*> colors \<*> shapes) >>= print
443+-- main = runAsyncT $ do
444+-- x <- each [3,2,1]
445+-- delay x
446+-- @
447+-- @
448+-- ThreadId 40: Delay 1
449+-- ThreadId 39: Delay 2
450+-- ThreadId 38: Delay 3
451+-- @
391452--
392−-- where
453+-- As you can see the code after the @each@ statement is run three times, once
454+-- for each value of @x@. All the three iterations are concurrent and run in
455+-- different threads. The iteration with least delay finishes first. When
456+-- compared to imperative programming, this can be viewed as a @for@ loop
457+-- with three concurrent iterations.
393458--
394−-- sizes = each [1, 2, 3]
395−-- colors = each ["red", "green", "blue"]
396−-- shapes = each ["triangle", "square", "circle"]
459+-- Concurrency is demand driven just as in the case of '<|'. When multiple
460+-- streams are composed using this style the iterations are triggered in a DFS
461+-- manner just like 'StreamT' i.e. nested iterations are executed before we
462+-- proceed to the next iteration at higher level. However, unlike 'StreamT'
463+-- more than one iterations may be started concurrently, and based on the
464+-- demand from the consumer.
465+--
466+-- @
467+-- main = runAsyncT $ do
468+-- x <- each [1,2]
469+-- y <- each [3,4]
470+-- liftIO $ putStrLn $ show (x, y)
397471-- @
472+-- @
473+-- (1,3)
474+-- (1,4)
475+-- (2,3)
476+-- (2,4)
477+-- @
478+--
479+-- You will notice that this is the monadic equivalent of the '<|' style
480+-- sum composition. The same caveats apply to this as the '<|' operation.
398481
399−-- $listt
482+-- $interleavedNesting
400483--
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.
405−-- For example the following program repeatedly reads strings from standard
406−-- input and echoes them to the standard output. This is like one infinite
407−-- loop:
484+-- When we interpret the monadic composition as 'InterleavedT' we get a serial
485+-- but fairly interleaved list-transformer like composition. The monadic
486+-- continuations or iterations of the outer loop are fairly interleaved with
487+-- the continuations or iterations of the inner loop.
408488--
409489-- @
410−-- import Streamly
411−-- import Data.Semigroup (cycle1)
412−--
413−-- main = runStreamT $ do
414−-- str <- cycle1 (liftIO getLine)
415−-- liftIO $ putStrLn str
490+-- main = runInterleavedT $ do
491+-- x <- each [1,2]
492+-- y <- each [3,4]
493+-- liftIO $ putStrLn $ show (x, y)
416494-- @
495+-- @
496+-- (1,3)
497+-- (2,3)
498+-- (1,4)
499+-- (2,4)
500+-- @
501+--
502+-- You will notice that this is the monadic equivalent of the '<=>' style
503+-- sum composition. The same caveats apply to this as the '<=>' operation.
417504
418−-- $monadicAlternatives
505+-- $fairlyConcurrentNesting
506+--
507+-- When we interpret the monadic composition as 'ParallelT' we get a
508+-- /concurrent/ list-transformer like composition just like 'AsyncT'. The
509+-- difference is that this is fully parallel with all iterations starting
510+-- concurrently instead of the demand driven concurrency of 'AsyncT'.
511+--
512+-- @
513+-- main = runParallelT $ do
514+-- x <- each [3,2,1]
515+-- delay x
516+-- @
517+-- @
518+-- ThreadId 40: Delay 1
519+-- ThreadId 39: Delay 2
520+-- ThreadId 38: Delay 3
521+-- @
419522--
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.
523+-- You will notice that this is the monadic equivalent of the '<|>' style
524+-- sum composition. The same caveats apply to this as the '<|>' operation.
525+
526+-- $monadExercise
424527--
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.
528+-- The streamly code is usually written in a way that is agnostic of the
529+-- specific monadic composition type. We use a polymorphic type with a
530+-- 'Streaming' type class constraint. When running the stream we can choose the
531+-- specific mode of composition. For example look at the following code.
429532--
430533-- @
431534-- import Streamly
@@ -443,85 +546,119 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
443546-- colors = each ["red", "green", "blue"]
444547-- shapes = each ["triangle", "square", "circle"]
445548-- @
446−
447−-- $regularSerial
448549--
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.
550+-- Now we can interpret this in whatever way we want:
454551--
455552-- @
456−-- main = runStreaming $ serially $ composed
553+-- main = runStreamT composed
554+-- main = runAsyncT composed
555+-- main = runInterleavedT composed
556+-- main = runParallelT composed
457557-- @
458558--
459−
460−-- $interleavedNesting
559+-- Equivalently, we can also write it using the type adapter combinators, like
560+-- this:
461561--
462562-- @
463−-- main = runStreaming $ interleaved $ composed
563+-- main = runStreaming $ serially $ composed
564+-- main = runStreaming $ asyncly $ composed
565+-- main = runStreaming $ interleaving $ composed
566+-- main = runStreaming $ parallely $ composed
464567-- @
568+--
569+-- As an exercise try to figure out the output of this code for each mode of
570+-- composition.
465571
466−-- $concurrentNesting
572+-- $functor
573+--
574+-- The simplest transformation on a stream is mapping a function on
575+-- all elements of the stream using 'fmap'.
467576--
468577-- @
469578-- import Streamly
470−-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
471−-- import Control.Concurrent
472579--
473−-- printWithTid s = do
474−-- threadDelay 500
475−-- tid <- myThreadId
476−-- putStrLn (show tid ++ ": " ++ s)
580+-- main = (toList $ serially $ fmap show $ each [1..10]) >>= print
581+-- @
582+
583+-- $applicative
477584--
478−-- main = runStreaming $ asyncly $ do
479−-- liftIO $ hSetBuffering stdout LineBuffering
480−-- sz <- sizes
481−-- cl <- colors
482−-- sh <- shapes
483−-- liftIO $ putStrLn $ show (sz, cl, sh)
585+-- Applicative is a special case of monad and behaves just like the monadic
586+-- composition. The following example runs all iterations serially and takes a
587+-- total 17 seconds (1 + 3 + 4 + 2 + 3 + 4):
484588--
485−-- where
589+-- @
590+-- s1 = d 1 <> d 2
591+-- s2 = d 3 <> d 4
592+-- d n = delay n >> return n
486593--
487−-- toStream xs = foldMapWith (<>) return xs
488−-- sizes = toStream [1, 2, 3]
489−-- colors = toStream ["red", "green", "blue"]
490−-- shapes = toStream ["triangle", "square", "circle"]
594+-- main = (toList . serially $ (,) \<$> s1 \<*> s2) >>= print
491595-- @
492−
493−-- $fairlyConcurrentNesting
596+-- @
597+-- ThreadId 36: Delay 1
598+-- ThreadId 36: Delay 3
599+-- ThreadId 36: Delay 4
600+-- ThreadId 36: Delay 2
601+-- ThreadId 36: Delay 3
602+-- ThreadId 36: Delay 4
603+-- [(1,3),(1,4),(2,3),(2,4)]
604+-- @
605+--
606+-- Similalrly interleaving runs the iterations in an interleaved order but
607+-- since it is serial it takes a total of 17 seconds:
494608--
495609-- @
496−-- import Streamly
497−-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
498−-- import Control.Concurrent
610+-- main = (toList . interleaving $ (,) \<$> s1 \<*> s2) >>= print
611+-- @
612+-- @
613+-- ThreadId 36: Delay 1
614+-- ThreadId 36: Delay 3
615+-- ThreadId 36: Delay 2
616+-- ThreadId 36: Delay 3
617+-- ThreadId 36: Delay 4
618+-- ThreadId 36: Delay 4
619+-- [(1,3),(2,3),(1,4),(2,4)]
620+-- @
499621--
500−-- printWithTid s = do
501−-- threadDelay 500
502−-- tid <- myThreadId
503−-- putStrLn (show tid ++ ": " ++ s)
622+-- 'AsyncT' can run the iterations concurrently and therefore takes a total
623+-- of 10 seconds (1 + 2 + 3 + 4):
504624--
505−-- main = runStreaming $ parallely $ do
506−-- liftIO $ hSetBuffering stdout LineBuffering
507−-- sz <- sizes
508−-- cl <- colors
509−-- sh <- shapes
510−-- liftIO $ putStrLn $ show (sz, cl, sh)
625+-- @
626+-- main = (toList . asyncly $ (,) \<$> s1 \<*> s2) >>= print
627+-- @
628+-- @
629+-- ThreadId 34: Delay 1
630+-- ThreadId 36: Delay 2
631+-- ThreadId 35: Delay 3
632+-- ThreadId 36: Delay 3
633+-- ThreadId 35: Delay 4
634+-- ThreadId 36: Delay 4
635+-- [(1,3),(2,3),(1,4),(2,4)]
636+-- @
511637--
512−-- where
638+-- Similalrly 'ParallelT' as well can run the iterations concurrently and
639+-- therefore takes a total of 10 seconds (1 + 2 + 3 + 4):
513640--
514−-- toStream xs = foldMapWith (<>) return xs
515−-- sizes = toStream [1, 2, 3]
516−-- colors = toStream ["red", "green", "blue"]
517−-- shapes = toStream ["triangle", "square", "circle"]
641+-- @
642+-- main = (toList . parallely $ (,) \<$> s1 \<*> s2) >>= print
643+-- @
644+-- @
645+-- ThreadId 34: Delay 1
646+-- ThreadId 36: Delay 2
647+-- ThreadId 35: Delay 3
648+-- ThreadId 36: Delay 3
649+-- ThreadId 35: Delay 4
650+-- ThreadId 36: Delay 4
651+-- [(1,3),(2,3),(1,4),(2,4)]
518652-- @
519653
520654-- $folding
521655-- Folds
522656
657+-- $compositionSummary
658+-- Summary of compositions
659+
523660-- $zipping
524−-- Applicative composition
661+-- Applicative composition, Parallel applicative
525662
526663-- $concurrent
527664--