@@ -6,10 +6,9 @@
6-- License : BSD3
6-- License : BSD3
7-- Maintainer : harendra.kumar@gmail.com
7-- Maintainer : harendra.kumar@gmail.com
10-- Asyncly combines streaming, logic programming, concurrency and reactive
9-- Asyncly combines streaming, logic programming, concurrency and reactive
11-- programming under a single abstraction with a concise API, giving
10-- 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.
14-- The 'AsyncT' monad operates on streams. Multiple streams of the same type
13-- The 'AsyncT' monad operates on streams. Multiple streams of the same type
15-- can be summed using different sum style operators. For example, '<>' appends
14-- can be summed using different sum style operators. For example, '<>' appends
@@ -24,8 +23,8 @@
24-- likened to nested loops in the imperative programming parlance. In addition
23-- likened to nested loops in the imperative programming parlance. In addition
25-- to the regular monadic bind operator '>>=', other operators are provided to
24-- to the regular monadic bind operator '>>=', other operators are provided to
26-- compose streams in alternate manners. For example, '>->' interleaves the
25-- compose streams in alternate manners. For example, '>->' interleaves the
27−-- iterations of outer and inner loops, '>>|' runs outer and inner loops in
26+-- iterations of a loop, '>>|' runs iterations in parallel and '>|>' runs all
28−-- parallel and '>|>' runs the outer and inner loops in fairly parallel manner.
27+-- iterations in fairly parallel manner.
30-- With asyncly you can write concurrent and streaming applications without
29-- With asyncly you can write concurrent and streaming applications without
31-- being aware of threads or synchronization. No explicit thread control is
30-- being aware of threads or synchronization. No explicit thread control is
@@ -43,9 +42,15 @@ module Asyncly.Tutorial
43 -- * AsyncT Monad Transformer
42 -- * AsyncT Monad Transformer
44 -- $transformer
43 -- $transformer
46− -- * Generating Streams (Monoidal Sum Composition)
45+ -- ** Generating Streams
47 -- $generating
46 -- $generating
48+ -- ** Eliminating Streams
51+ -- * Semigroup Style Composition
49 -- ** Serial composition (Append)
54 -- ** Serial composition (Append)
50 -- $serial
55 -- $serial
@@ -61,22 +66,38 @@ module Asyncly.Tutorial
61 -- ** Custom composition
66 -- ** Custom composition
62 -- $custom
67 -- $custom
64− -- * Looping Over Streams (Monadic Product Composition)
69+ -- * Monoid Style composition
72+ -- * Transforming Streams
66− -- ** List Transformer
75+ -- ** Functor
69− -- ** Interleaved Nesting
84+ -- ** Interleaved Iterations
70 -- $interleavedNesting
85 -- $interleavedNesting
72− -- ** Concurrent Nesting
87+ -- ** Concurrent Iterations
73 -- $concurrentNesting
88 -- $concurrentNesting
75− -- ** Fairly Concurrent Nesting
90+ -- ** Fairly Concurrent Iterations
76 -- $fairlyConcurrentNesting
91 -- $fairlyConcurrentNesting
78− -- * Applicative Composition
93+ -- * Folding Streams
79− -- $applicative
94+ -- $folding
99+ -- * Concurrent Programming Examples
81 -- * Reactive Programming
102 -- * Reactive Programming
82 -- $reactive
103 -- $reactive
@@ -84,11 +105,14 @@ module Asyncly.Tutorial
84 -- * State Machine Model
105 -- * State Machine Model
85 -- $statemachine
106 -- $statemachine
87 -- * Interworking with Streaming Libraries
111 -- * Interworking with Streaming Libraries
88 -- $interwork
112 -- $interwork
90− -- * Performance
114+ -- * Comparison with Existing Packages
91− -- $performance
115+ -- $comparison
@@ -97,55 +121,57 @@ import Asyncly.Prelude
97-- import Asyncly.Examples
121-- import Asyncly.Examples
98import Data.Semigroup
122import Data.Semigroup
99import Control.Applicative
123import Control.Applicative
100−import Control.Monad.IO.Class (MonadIO(..))
125+import Control.Monad.IO.Class (MonadIO(..))
101−import Control.Monad.Trans.Class (MonadTrans (lift))
126+import Control.Monad.Trans.Class (MonadTrans (lift))
103-- $transformer
128-- $transformer
105-- 'AsyncT' is a monad transformer, the type 'AsyncT m a' represents a stream
130-- '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
131+-- of values of type 'a' in the underlying monad 'm'. In this section we
107−-- 'lift' combinator to lift values from the immediately lower monad in the
132+-- describe how to create new values of type 'AsyncT' in the first place and
108−-- transformer stack or 'liftIO' to lift values from IO. 'runAsyncly' runs the
133+-- how to eliminate or run a composed 'AsyncT' computation. Later sections
109−-- monad discarding any results, 'toList' can be used to collect the results in
134+-- discuss how to compose existing AsyncT streams or computations to create
110−-- a list. You can also use 'uncons' to extract results from 'AsyncT'. For
135+-- composite AsyncT streams.
113−-- @
139+-- Pure values can be placed into the stream type using 'return' or 'pure'.
114−-- import Asyncly
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.
117−-- runAsyncly $ liftIO $ putStrLn "Hello world!"
148+-- liftIO $ putStrLn "Hello world!"
118−-- xs \<- toList $ return 1 <> return 2
122−-- $generating
151+-- $eliminating
124−-- AsyncT streams are generated using either 'return' on pure values or by
153+-- 'runAsyncly' runs composed actions in the 'AsyncT' monad, lowering the type
125−-- using 'lift' or a variant of it or by folding existing 'AsyncT' streams with
154+-- into the underlying monad and discarding the values generated by the stream;
126−-- a suitable sum operator. Some fold utilities are provided by the library for
155+-- 'toList' collects the resulting stream in a list. 'uncons' can be used
127−-- convenience:
156+-- to deconstruct an 'AsyncT' stream into head and tail.
129−-- * 'foldWith' folds a Foldable container of AsyncT computations using the
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.
136-- import Asyncly
159-- import Asyncly
138-- main = do
161-- main = do
139-- runAsyncly $ liftIO $ putStrLn "Hello world!"
162-- runAsyncly $ liftIO $ putStrLn "Hello world!"
140−-- xs \<- toList $ return 1 <> return 2; print xs
163+-- xs \<- toList $ return 1 <> return 2
141−-- xs \<- toList $ foldWith (<>) (map return [1..10]); print xs
164+-- print xs
142−-- xs \<- toList $ foldMapWith (<>) return [1..10]; print xs
143−-- xs \<- toList $ forEachWith (<>) [1..10] return; print xs
145−-- The following sections illustrate different operators that we can use to
166+
146−-- fold AsyncT streams. We will use a trivial example of composing a few timer
167+-- $semigroup
147−-- using the timer function defined below. In general these could be
168+-- AsyncT streams of the same type can be combined into a composite stream in
148−-- arbitrarily complex computations and any number of them.
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.
151-- import Asyncly
177-- import Asyncly
@@ -274,7 +300,6 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
274-- get s = liftIO (httpNoBody (parseRequest_ s) >> putStrLn (show s))
300-- get s = liftIO (httpNoBody (parseRequest_ s) >> putStrLn (show s))
278-- $custom
303-- $custom
280-- The 'async' API can be used to create references to asynchronously running
305-- The 'async' API can be used to create references to asynchronously running
@@ -286,122 +311,234 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
286-- This can be achieved using async references to streams. See
311-- This can be achieved using async references to streams. See
287-- 'Asyncly.Examples.MergeSortedStreams'.
312-- 'Asyncly.Examples.MergeSortedStreams'.
289−-- $listt
314+-- $monoid
291−-- The previous section discussed ways to purely compose streams using sum
316+-- Containers can be folded using a suitable identity ('mempty',
292−-- style operators. In this section we will explore how to transform streams
317+-- 'empty', 'mzero') and one of the binary operators mentioned earlier. Some
293−-- using monadic composition.
318+-- fold utilities are provided by the library for convenience:
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.
302−-- Here is an example program that repeatedly reads strings from standard input
320+-- * 'foldWith' folds a 'Foldable' container of AsyncT computations using the
303−-- and echoes them to the standard output:
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.
306-- import Asyncly
327-- import Asyncly
307−-- import Data.Semigroup (cycle1)
309−-- main = runAsyncly $ cycle1 (liftIO getLine) >>= liftIO . putStrLn
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
311−-- Here we perform a simple multi-threaded map-reduce by squaring each number
336+
312−-- in a separate thread and then summing the squares:
337+-- $transforming
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.
351+-- The simplest way to transform an 'AsyncT' stream is by mapping a function on
352+-- all elements of the stream using 'fmap'.
315−-- import Control.Applicative ((\<|\>), empty)
316−-- import Data.List (sum)
317-- import Asyncly
355-- import Asyncly
319-- main = do
357-- main = do
320−-- squares <- wait $ do
358+-- let nums = foldMapWith (<>) return [1..10]
321−-- x <- foldl (\<|\>) empty $ map return [1..100]
359+-- let strings = fmap show nums
322−-- return (x * x)
360+-- (toList $ fmap putStrLn strings) >>= sequence
323−-- print . sum $ squares
361+-- @
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.
374+-- The following example prints all combinations of size, color and shape:
377+-- main = (toList $ (,,) \<$> sizes \<*> colors \<*> shapes) >>= print
326−-- Here two random number generation loops run in parallel so two numbers are
379+-- where
327−-- printed every second. Note that the threadId printed for each is different:
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"]
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.
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
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)
335-- import Asyncly
398-- import Asyncly
399+-- import Data.Semigroup (cycle1)
337−-- main = wait_ $ do
401+-- main = runAsyncly $ do
338−-- liftIO $ hSetBuffering stdout LineBuffering
402+-- str <- cycle1 (liftIO getLine)
339−-- x <- loop \"A" \<|\> loop \"B"
403+-- liftIO $ putStrLn str
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.
411+-- main = runAsyncly $ do
340−-- liftIO $ myThreadId >>= putStr . show
415+-- liftIO $ putStrLn $ show (sz, cl, sh)
346−-- loop name = do
419+-- toStream xs = foldMapWith (<>) return xs
347−-- liftIO $ threadDelay 1000000
420+-- sizes = toStream [1, 2, 3]
348−-- rnd <- liftIO (randomIO :: IO Int)
421+-- colors = toStream ["red", "green", "blue"]
349−-- return (name, rnd) \<|\> loop name
422+-- shapes = toStream ["triangle", "square", "circle"]
352-- $interleavedNesting
426-- $interleavedNesting
353−-- interleavedNesting
355−-- $concurrentNesting
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
360−-- import Path.IO (listDir, getCurrentDir)
361-- import Asyncly
433-- import Asyncly
363−-- main = runAsyncly $ getCurrentDir >>= readdir
435+-- main = runAsyncly $
364−-- where readdir d = do
436+-- sizes >-> \\sz ->
365−-- (dirs, files) <- lift $ listDir d
437+-- colors >-> \\cl ->
438+-- shapes >-> \\sh ->
366−-- liftIO $ mapM_ putStrLn $ map show files
439+-- liftIO $ putStrLn $ show (sz, cl, sh)
367−-- foldr (<>) mempty $ map readdir dirs
440+--
443+-- toStream xs = foldMapWith (<>) return xs
444+-- sizes = toStream [1, 2, 3]
445+-- colors = toStream ["red", "green", "blue"]
446+-- shapes = toStream ["triangle", "square", "circle"]
369−-- Here the two loops are serially composed. For each value yielded by loop A,
448+
370−-- loop B is executed. Four results are printed, all four run in separate
449+-- $concurrentNesting
371−-- parallel threads. The composition is like ListT except that this is
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)
380-- import Asyncly
452-- import Asyncly
453+-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
454+-- import Control.Concurrent
456+-- printWithTid s = do
459+-- putStrLn (show tid ++ ": " ++ s)
382−-- main = wait_ $ do
461+-- main = runAsyncly $ do
383-- liftIO $ hSetBuffering stdout LineBuffering
462-- liftIO $ hSetBuffering stdout LineBuffering
384−-- x <- loop "A " 2
463+-- sizes >>| \\sz ->
385−-- y <- loop "B " 2
464+-- colors >>| \\cl ->
386−-- liftIO $ myThreadId >>= putStr . show
465+-- shapes >>| \\sh ->
387−-- >> putStr " "
466+-- liftIO $ printWithTid $ show (sz, cl, sh)
392−-- loop name n = do
470+-- toStream xs = foldMapWith (<>) return xs
393−-- rnd <- liftIO (randomIO :: IO Int)
471+-- sizes = toStream [1, 2, 3]
394−-- let result = (name ++ show rnd)
472+-- colors = toStream ["red", "green", "blue"]
395−-- repeat = if n > 1 then loop name (n - 1) else empty
473+-- shapes = toStream ["triangle", "square", "circle"]
396−-- in (return result) \<|\> repeat
399-- $fairlyConcurrentNesting
476-- $fairlyConcurrentNesting
400−-- fairlyConcurrentNesting
477+--
480+-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
481+-- import Control.Concurrent
483+-- printWithTid s = do
486+-- putStrLn (show tid ++ ": " ++ s)
488+-- main = runAsyncly $ do
489+-- liftIO $ hSetBuffering stdout LineBuffering
491+-- colors >|> \\cl ->
492+-- shapes >|> \\sh ->
493+-- liftIO $ printWithTid $ show (sz, cl, sh)
497+-- toStream xs = foldMapWith (<>) return xs
498+-- sizes = toStream [1, 2, 3]
499+-- colors = toStream ["red", "green", "blue"]
500+-- shapes = toStream ["triangle", "square", "circle"]
402−-- $applicative
503+-- $folding
403-- Applicative composition
507-- Applicative composition
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.
515+-- In the following example, we square each number concurrently but then
516+-- sum and print them serially:
520+-- import Data.List (sum)
523+-- squares \<- toList $ foldMapWith (<|) (\x -\> return $ x * x) [1..100]
524+-- print $ sum squares
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
533+-- import Data.List (sum)
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)
405-- $reactive
542-- $reactive
407-- Let us see a reactive programming example:
544-- Let us see a reactive programming example:
@@ -430,7 +567,7 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
431-- game :: (MonadAsync m, MonadState Int m) => AsyncT m ()
568-- game :: (MonadAsync m, MonadState Int m) => AsyncT m ()
432-- game = do
569-- game = do
433−-- event <- userAction <|> acidRain
570+-- event \<- userAction \<|> acidRain
434-- case event of
571-- case event of
435-- Harm n -> modify $ \h -> h - n
572-- Harm n -> modify $ \h -> h - n
436-- Heal n -> modify $ \h -> h + n
573-- Heal n -> modify $ \h -> h + n