@@ -6,10 +6,9 @@
66-- License : BSD3
77-- Maintainer : harendra.kumar@gmail.com
88--
9−--
109-- Asyncly combines streaming, logic programming, concurrency and reactive
1110-- programming under a single abstraction with a concise API, giving
12−-- programmers a powerful tool for wide variety of applications.
11+-- programmers a simple yet powerful tool for wide variety of applications.
1312--
1413-- The 'AsyncT' monad operates on streams. Multiple streams of the same type
1514-- can be summed using different sum style operators. For example, '<>' appends
@@ -24,8 +23,8 @@
2423-- likened to nested loops in the imperative programming parlance. In addition
2524-- to the regular monadic bind operator '>>=', other operators are provided to
2625-- compose streams in alternate manners. For example, '>->' interleaves the
27−-- iterations of outer and inner loops, '>>|' runs outer and inner loops in
28−-- parallel and '>|>' runs the outer and inner loops in fairly parallel manner.
26+-- iterations of a loop, '>>|' runs iterations in parallel and '>|>' runs all
27+-- iterations in fairly parallel manner.
2928--
3029-- With asyncly you can write concurrent and streaming applications without
3130-- being aware of threads or synchronization. No explicit thread control is
@@ -43,9 +42,15 @@ module Asyncly.Tutorial
4342 -- * AsyncT Monad Transformer
4443 -- $transformer
4544
46− -- * Generating Streams (Monoidal Sum Composition)
45+ -- ** Generating Streams
4746 -- $generating
4847
48+ -- ** Eliminating Streams
49+ -- $eliminating
50+
51+ -- * Semigroup Style Composition
52+ -- $semigroup
53+
4954 -- ** Serial composition (Append)
5055 -- $serial
5156
@@ -61,22 +66,38 @@ module Asyncly.Tutorial
6166 -- ** Custom composition
6267 -- $custom
6368
64− -- * Looping Over Streams (Monadic Product Composition)
69+ -- * Monoid Style composition
70+ -- $monoid
71+
72+ -- * Transforming Streams
73+ -- $transforming
6574
66− -- ** List Transformer
75+ -- ** Functor
76+ -- $functor
77+
78+ -- ** Applicative
79+ -- $applicative
80+
81+ -- ** Monad
6782 -- $listt
6883
69− -- ** Interleaved Nesting
84+ -- ** Interleaved Iterations
7085 -- $interleavedNesting
7186
72− -- ** Concurrent Nesting
87+ -- ** Concurrent Iterations
7388 -- $concurrentNesting
7489
75− -- ** Fairly Concurrent Nesting
90+ -- ** Fairly Concurrent Iterations
7691 -- $fairlyConcurrentNesting
7792
78− -- * Applicative Composition
79− -- $applicative
93+ -- * Folding Streams
94+ -- $folding
95+
96+ -- * Zipping Streams
97+ -- $zipping
98+
99+ -- * Concurrent Programming Examples
100+ -- $concurrent
80101
81102 -- * Reactive Programming
82103 -- $reactive
@@ -84,11 +105,14 @@ module Asyncly.Tutorial
84105 -- * State Machine Model
85106 -- $statemachine
86107
108+ -- * Performance
109+ -- $performance
110+
87111 -- * Interworking with Streaming Libraries
88112 -- $interwork
89113
90− -- * Performance
91− -- $performance
114+ -- * Comparison with Existing Packages
115+ -- $comparison
92116 )
93117where
94118
@@ -97,55 +121,57 @@ import Asyncly.Prelude
97121-- import Asyncly.Examples
98122import Data.Semigroup
99123import Control.Applicative
100−import Control.Monad.IO.Class (MonadIO(..))
101−import Control.Monad.Trans.Class (MonadTrans (lift))
124+import Control.Monad
125+import Control.Monad.IO.Class (MonadIO(..))
126+import Control.Monad.Trans.Class (MonadTrans (lift))
102127
103128-- $transformer
104129--
105130-- 'AsyncT' is a monad transformer, the type 'AsyncT m a' represents a stream
106−-- of values of type 'a' in the underlying monad 'm'. As usual you can use the
107−-- 'lift' combinator to lift values from the immediately lower monad in the
108−-- transformer stack or 'liftIO' to lift values from IO. 'runAsyncly' runs the
109−-- monad discarding any results, 'toList' can be used to collect the results in
110−-- a list. You can also use 'uncons' to extract results from 'AsyncT'. For
111−-- example:
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.
136+
137+-- $generating
112138--
113−-- @
114−-- import Asyncly
139+-- 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'
141+-- combinator. In a transformer stack we can lift actions from the lower monad
142+-- using the 'lift' combinator.
115143--
116−-- main = do
117−-- runAsyncly $ liftIO $ putStrLn "Hello world!"
118−-- xs \<- toList $ return 1 <> return 2
119−-- print xs
144+-- @
145+-- return 1
146+-- @
147+-- @
148+-- liftIO $ putStrLn "Hello world!"
120149-- @
121150
122−-- $generating
151+-- $eliminating
123152--
124−-- AsyncT streams are generated using either 'return' on pure values or by
125−-- using 'lift' or a variant of it or by folding existing 'AsyncT' streams with
126−-- a suitable sum operator. Some fold utilities are provided by the library for
127−-- convenience:
128−--
129−-- * 'foldWith' folds a Foldable container of AsyncT computations using the
130−-- given function.
131−-- * 'foldMapWith' folds like foldWith but also maps a function before folding.
132−-- * 'forEachWith' is like foldMapwith but the container argument comes before
133−-- the function argument.
153+-- 'runAsyncly' runs composed actions in the 'AsyncT' 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 an 'AsyncT' stream into head and tail.
134157--
135158-- @
136159-- import Asyncly
137160--
138161-- main = do
139162-- runAsyncly $ liftIO $ putStrLn "Hello world!"
140−-- xs \<- toList $ return 1 <> return 2; print xs
141−-- xs \<- toList $ foldWith (<>) (map return [1..10]); print xs
142−-- xs \<- toList $ foldMapWith (<>) return [1..10]; print xs
143−-- xs \<- toList $ forEachWith (<>) [1..10] return; print xs
163+-- xs \<- toList $ return 1 <> return 2
164+-- print xs
144165-- @
145−-- The following sections illustrate different operators that we can use to
146−-- fold AsyncT streams. We will use a trivial example of composing a few timer
147−-- using the timer function defined below. In general these could be
148−-- arbitrarily complex computations and any number of them.
166+
167+-- $semigroup
168+-- AsyncT streams of the same type can be combined into a composite stream in
169+-- many different ways using semigroup style binary composition operators
170+-- ('<>', '<=>', '<|', '<|>', 'mplus').
171+-- We will use the following utility functions to illustrate these operators in
172+-- the following sections. The @traced@ function prints the 'threadId' of the
173+-- thread in which the computation runs and the @timer@ function adds a delay
174+-- specified by the argument in seconds and then prints the argument.
149175--
150176-- @
151177-- import Asyncly
@@ -274,7 +300,6 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
274300-- get s = liftIO (httpNoBody (parseRequest_ s) >> putStrLn (show s))
275301-- @
276302
277−
278303-- $custom
279304--
280305-- The 'async' API can be used to create references to asynchronously running
@@ -286,122 +311,234 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
286311-- This can be achieved using async references to streams. See
287312-- 'Asyncly.Examples.MergeSortedStreams'.
288313
289−-- $listt
314+-- $monoid
290315--
291−-- The previous section discussed ways to purely compose streams using sum
292−-- style operators. In this section we will explore how to transform streams
293−-- using monadic composition.
294−-- The above examples just used applicative or alternative compositions. They
295−-- can be done equivalently using async package as well. However the real power
296−-- of asyncly is in the monadic composition. Monadic composition allows
297−-- creating state machines or event networks or multiplying streams whatever
298−-- way you want to think about it. It is a very powerful general, concurrent
299−-- and reactive programming tool.
300−
316+-- Containers can be folded using a suitable identity ('mempty',
317+-- 'empty', 'mzero') and one of the binary operators mentioned earlier. Some
318+-- fold utilities are provided by the library for convenience:
301319--
302−-- Here is an example program that repeatedly reads strings from standard input
303−-- and echoes them to the standard output:
320+-- * 'foldWith' folds a 'Foldable' container of AsyncT computations using the
321+-- given function.
322+-- * 'foldMapWith' folds like foldWith but also maps a function before folding.
323+-- * 'forEachWith' is like foldMapwith but the container argument comes before
324+-- the function argument.
304325--
305326-- @
306327-- import Asyncly
307−-- import Data.Semigroup (cycle1)
308328--
309−-- main = runAsyncly $ cycle1 (liftIO getLine) >>= liftIO . putStrLn
329+-- main = do
330+-- runAsyncly $ liftIO $ putStrLn "Hello world!"
331+-- xs \<- toList $ return 1 <> return 2; print xs
332+-- xs \<- toList $ foldWith (<>) (map return [1..10]); print xs
333+-- xs \<- toList $ foldMapWith (<>) return [1..10]; print xs
334+-- xs \<- toList $ forEachWith (<>) [1..10] return; print xs
310335-- @
311−-- Here we perform a simple multi-threaded map-reduce by squaring each number
312−-- in a separate thread and then summing the squares:
336+
337+-- $transforming
338+--
339+-- The previous section discussed ways to compose streams without any
340+-- transformation using sum style operators. In this section we will explore
341+-- how to transform streams using 'Functor', 'Applicative' or 'Monad'
342+-- style compositions. The applicative and monad composition of 'AsyncT' is
343+-- exactly the same as a list transformer.
344+-- For simplicity of illustration we are using streams of
345+-- pure values in the following examples. However, the real application of
346+-- AsyncT arises when these streams are generated using monadic actions. In the
347+-- pure stream cases AsyncT is equivalent to the list monad.
348+
349+-- $functor
350+--
351+-- The simplest way to transform an 'AsyncT' stream is by mapping a function on
352+-- all elements of the stream using 'fmap'.
313353--
314354-- @
315−-- import Control.Applicative ((\<|\>), empty)
316−-- import Data.List (sum)
317355-- import Asyncly
318356--
319357-- main = do
320−-- squares <- wait $ do
321−-- x <- foldl (\<|\>) empty $ map return [1..100]
322−-- return (x * x)
323−-- print . sum $ squares
358+-- let nums = foldMapWith (<>) return [1..10]
359+-- let strings = fmap show nums
360+-- (toList $ fmap putStrLn strings) >>= sequence
361+-- @
362+
363+-- $applicative
364+--
365+-- You can compose two or more streams using a function in 'Applicative' manner
366+-- as well. In functional programmer's parlance the applicative implements
367+-- non-determinism, exploring all possible combination of choices from both the
368+-- streams. From an imperative programmer's point of view the applicative
369+-- behaves like nested loops i.e. for each element in the first argument and
370+-- for each element in the second argument apply the function. Note that the
371+-- behavior of applicative instance of 'AsyncT' is exactly the same as that of
372+-- a list transformer.
373+--
374+-- The following example prints all combinations of size, color and shape:
375+--
324376-- @
377+-- main = (toList $ (,,) \<$> sizes \<*> colors \<*> shapes) >>= print
325378--
326−-- Here two random number generation loops run in parallel so two numbers are
327−-- printed every second. Note that the threadId printed for each is different:
379+-- where
380+--
381+-- toStream xs = foldMapWith (<>) return xs
382+-- sizes = toStream [1, 2, 3]
383+-- colors = toStream ["red", "green", "blue"]
384+-- shapes = toStream ["triangle", "square", "circle"]
385+-- @
386+
387+-- $listt
388+--
389+-- The monadic composition is similar to Applicative composition and just like
390+-- a list transformer. It is like nested loops in the imperative parlance. The
391+-- monadic continuation is performed for each element in a stream.
392+--
393+-- For example the following program repeatedly reads strings from standard
394+-- input and echoes them to the standard output. This is like one infinite
395+-- loop:
328396--
329397-- @
330−-- import Control.Applicative ((\<|\>))
331−-- import Control.Concurrent (myThreadId, threadDelay)
332−-- import Control.Monad.IO.Class (liftIO)
333−-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
334−-- import System.Random (randomIO)
335398-- import Asyncly
399+-- import Data.Semigroup (cycle1)
336400--
337−-- main = wait_ $ do
338−-- liftIO $ hSetBuffering stdout LineBuffering
339−-- x <- loop \"A" \<|\> loop \"B"
340−-- liftIO $ myThreadId >>= putStr . show
341−-- >> putStr " "
342−-- >> print x
401+-- main = runAsyncly $ do
402+-- str <- cycle1 (liftIO getLine)
403+-- liftIO $ putStrLn str
404+-- @
405+--
406+-- This example is equivalent to three nested loops, the first one for sizes,
407+-- the second one for colors and the third one for shapes. It prints all
408+-- combinations of size, color and shape.
409+--
410+-- @
411+-- main = runAsyncly $ do
412+-- sz <- sizes
413+-- cl <- colors
414+-- sh <- shapes
415+-- liftIO $ putStrLn $ show (sz, cl, sh)
343416--
344417-- where
345418--
346−-- loop name = do
347−-- liftIO $ threadDelay 1000000
348−-- rnd <- liftIO (randomIO :: IO Int)
349−-- return (name, rnd) \<|\> loop name
419+-- toStream xs = foldMapWith (<>) return xs
420+-- sizes = toStream [1, 2, 3]
421+-- colors = toStream ["red", "green", "blue"]
422+-- shapes = toStream ["triangle", "square", "circle"]
350423-- @
424+--
351425
352426-- $interleavedNesting
353−-- interleavedNesting
354−
355−-- $concurrentNesting
356427--
357−-- Let's take an example of listing a directory tree:
428+-- Instead of the regular bind operation (>>=) we can use the interleaved bind
429+-- operator (>->) to interleave the iterations of the nested loops. For
430+-- example:
358431--
359432-- @
360−-- import Path.IO (listDir, getCurrentDir)
361433-- import Asyncly
362434--
363−-- main = runAsyncly $ getCurrentDir >>= readdir
364−-- where readdir d = do
365−-- (dirs, files) <- lift $ listDir d
366−-- liftIO $ mapM_ putStrLn $ map show files
367−-- foldr (<>) mempty $ map readdir dirs
435+-- main = runAsyncly $
436+-- sizes >-> \\sz ->
437+-- colors >-> \\cl ->
438+-- shapes >-> \\sh ->
439+-- liftIO $ putStrLn $ show (sz, cl, sh)
440+--
441+-- where
442+--
443+-- toStream xs = foldMapWith (<>) return xs
444+-- sizes = toStream [1, 2, 3]
445+-- colors = toStream ["red", "green", "blue"]
446+-- shapes = toStream ["triangle", "square", "circle"]
368447-- @
369−-- Here the two loops are serially composed. For each value yielded by loop A,
370−-- loop B is executed. Four results are printed, all four run in separate
371−-- parallel threads. The composition is like ListT except that this is
372−-- concurrent:
448+
449+-- $concurrentNesting
373450--
374451-- @
375−-- import Control.Applicative ((\<|\>), empty)
376−-- import Control.Concurrent (myThreadId)
377−-- import Control.Monad.IO.Class (liftIO)
378−-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
379−-- import System.Random (randomIO)
380452-- import Asyncly
453+-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
454+-- import Control.Concurrent
455+--
456+-- printWithTid s = do
457+-- threadDelay 500
458+-- tid <- myThreadId
459+-- putStrLn (show tid ++ ": " ++ s)
381460--
382−-- main = wait_ $ do
461+-- main = runAsyncly $ do
383462-- liftIO $ hSetBuffering stdout LineBuffering
384−-- x <- loop "A " 2
385−-- y <- loop "B " 2
386−-- liftIO $ myThreadId >>= putStr . show
387−-- >> putStr " "
388−-- >> print (x, y)
463+-- sizes >>| \\sz ->
464+-- colors >>| \\cl ->
465+-- shapes >>| \\sh ->
466+-- liftIO $ printWithTid $ show (sz, cl, sh)
389467--
390468-- where
391469--
392−-- loop name n = do
393−-- rnd <- liftIO (randomIO :: IO Int)
394−-- let result = (name ++ show rnd)
395−-- repeat = if n > 1 then loop name (n - 1) else empty
396−-- in (return result) \<|\> repeat
470+-- toStream xs = foldMapWith (<>) return xs
471+-- sizes = toStream [1, 2, 3]
472+-- colors = toStream ["red", "green", "blue"]
473+-- shapes = toStream ["triangle", "square", "circle"]
397474-- @
398475
399476-- $fairlyConcurrentNesting
400−-- fairlyConcurrentNesting
477+--
478+-- @
479+-- import Asyncly
480+-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
481+-- import Control.Concurrent
482+--
483+-- printWithTid s = do
484+-- threadDelay 500
485+-- tid <- myThreadId
486+-- putStrLn (show tid ++ ": " ++ s)
487+--
488+-- main = runAsyncly $ do
489+-- liftIO $ hSetBuffering stdout LineBuffering
490+-- sizes >|> \\sz ->
491+-- colors >|> \\cl ->
492+-- shapes >|> \\sh ->
493+-- liftIO $ printWithTid $ show (sz, cl, sh)
494+--
495+-- where
496+--
497+-- toStream xs = foldMapWith (<>) return xs
498+-- sizes = toStream [1, 2, 3]
499+-- colors = toStream ["red", "green", "blue"]
500+-- shapes = toStream ["triangle", "square", "circle"]
501+-- @
401502
402−-- $applicative
503+-- $folding
504+-- Folds
505+
506+-- $zipping
403507-- Applicative composition
404508
509+-- $concurrent
510+--
511+-- There are two ways to achieve concurrency. We can generate individual
512+-- elements of a stream concurrently by folding with parallel composition
513+-- operators i.e. '<|' or '<|>'. 'forEachWith' can be useful in such cases.
514+--
515+-- In the following example, we square each number concurrently but then
516+-- sum and print them serially:
517+--
518+-- @
519+-- import Asyncly
520+-- import Data.List (sum)
521+--
522+-- main = do
523+-- squares \<- toList $ foldMapWith (<|) (\x -\> return $ x * x) [1..100]
524+-- print $ sum squares
525+-- @
526+--
527+-- The following example not just computes the squares concurrently but also
528+-- computes the square root of their sums concurrently by using the parallel
529+-- monadic bind.
530+--
531+-- @
532+-- import Asyncly
533+-- import Data.List (sum)
534+--
535+-- main = do
536+-- z \<- toList $ foldMapWith (\<|) (\\x -> return $ x * x) [1..100] >>| \\xsq ->
537+-- foldMapWith (\<|) (\\x -> return $ x * x) [1..100] >>| \\ysq ->
538+-- return $ sqrt (xsq + ysq)
539+-- print $ sum z
540+-- @
541+
405542-- $reactive
406543--
407544-- Let us see a reactive programming example:
@@ -430,7 +567,7 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
430567--
431568-- game :: (MonadAsync m, MonadState Int m) => AsyncT m ()
432569-- game = do
433−-- event <- userAction <|> acidRain
570+-- event \<- userAction \<|> acidRain
434571-- case event of
435572-- Harm n -> modify $ \h -> h - n
436573-- Heal n -> modify $ \h -> h + n