on master help sign in
update tutorial
1b09d51Harendra Kumar
src/Asyncly/Tutorial.hs+258−121
@@ -6,10 +6,9 @@
6-- License : BSD3
6-- License : BSD3
7-- Maintainer : harendra.kumar@gmail.com
7-- Maintainer : harendra.kumar@gmail.com
8--
8--
9−--
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.
13--
12--
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.
29--
28--
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
45
44
46− -- * Generating Streams (Monoidal Sum Composition)
45+ -- ** Generating Streams
47 -- $generating
46 -- $generating
48
47
48+ -- ** Eliminating Streams
49+ -- $eliminating
50+
51+ -- * Semigroup Style Composition
52+ -- $semigroup
53+
49 -- ** Serial composition (Append)
54 -- ** Serial composition (Append)
50 -- $serial
55 -- $serial
51
56
@@ -61,22 +66,38 @@ module Asyncly.Tutorial
61 -- ** Custom composition
66 -- ** Custom composition
62 -- $custom
67 -- $custom
63
68
64− -- * Looping Over Streams (Monadic Product Composition)
69+ -- * Monoid Style composition
70+ -- $monoid
71+
72+ -- * Transforming Streams
73+ -- $transforming
65
74
66− -- ** List Transformer
75+ -- ** Functor
76+ -- $functor
77+
78+ -- ** Applicative
79+ -- $applicative
80+
81+ -- ** Monad
67 -- $listt
82 -- $listt
68
83
69− -- ** Interleaved Nesting
84+ -- ** Interleaved Iterations
70 -- $interleavedNesting
85 -- $interleavedNesting
71
86
72− -- ** Concurrent Nesting
87+ -- ** Concurrent Iterations
73 -- $concurrentNesting
88 -- $concurrentNesting
74
89
75− -- ** Fairly Concurrent Nesting
90+ -- ** Fairly Concurrent Iterations
76 -- $fairlyConcurrentNesting
91 -- $fairlyConcurrentNesting
77
92
78− -- * Applicative Composition
93+ -- * Folding Streams
79− -- $applicative
94+ -- $folding
95+
96+ -- * Zipping Streams
97+ -- $zipping
98+
99+ -- * Concurrent Programming Examples
100+ -- $concurrent
80
101
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
86
107
108+ -- * Performance
109+ -- $performance
110+
87 -- * Interworking with Streaming Libraries
111 -- * Interworking with Streaming Libraries
88 -- $interwork
112 -- $interwork
89
113
90− -- * Performance
114+ -- * Comparison with Existing Packages
91− -- $performance
115+ -- $comparison
92 )
116 )
93where
117where
94
118
@@ -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
124+import Control.Monad
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))
102
127
103-- $transformer
128-- $transformer
104--
129--
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.
111−-- example:
136+
137+-- $generating
112--
138--
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.
115--
143--
116−-- main = do
144+-- @
145+-- return 1
146+-- @
147+-- @
117−-- runAsyncly $ liftIO $ putStrLn "Hello world!"
148+-- liftIO $ putStrLn "Hello world!"
118−-- xs \<- toList $ return 1 <> return 2
119−-- print xs
120-- @
149-- @
121
150
122−-- $generating
151+-- $eliminating
123--
152--
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.
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.
134--
157--
135-- @
158-- @
136-- import Asyncly
159-- import Asyncly
137--
160--
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
144-- @
165-- @
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.
149--
175--
150-- @
176-- @
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))
275-- @
301-- @
276
302
277−
278-- $custom
303-- $custom
279--
304--
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'.
288
313
289−-- $listt
314+-- $monoid
290--
315--
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.
300−
301--
319--
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.
304--
325--
305-- @
326-- @
306-- import Asyncly
327-- import Asyncly
307−-- import Data.Semigroup (cycle1)
308--
328--
329+-- main = do
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
310-- @
335-- @
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
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'.
313--
353--
314-- @
354-- @
315−-- import Control.Applicative ((\<|\>), empty)
316−-- import Data.List (sum)
317-- import Asyncly
355-- import Asyncly
318--
356--
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+-- @
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+--
324-- @
376-- @
377+-- main = (toList $ (,,) \<$> sizes \<*> colors \<*> shapes) >>= print
325--
378--
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"]
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:
328--
396--
329-- @
397-- @
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)
336--
400--
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
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
340−-- liftIO $ myThreadId >>= putStr . show
415+-- liftIO $ putStrLn $ show (sz, cl, sh)
341−-- >> putStr " "
342−-- >> print x
343--
416--
344-- where
417-- where
345--
418--
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"]
350-- @
423-- @
424+--
351
425
352-- $interleavedNesting
426-- $interleavedNesting
353−-- interleavedNesting
354−
355−-- $concurrentNesting
356--
427--
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:
358--
431--
359-- @
432-- @
360−-- import Path.IO (listDir, getCurrentDir)
361-- import Asyncly
433-- import Asyncly
362--
434--
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+--
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"]
368-- @
447-- @
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
372−-- concurrent:
373--
450--
374-- @
451-- @
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
455+--
456+-- printWithTid s = do
457+-- threadDelay 500
458+-- tid <- myThreadId
459+-- putStrLn (show tid ++ ": " ++ s)
381--
460--
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)
388−-- >> print (x, y)
389--
467--
390-- where
468-- where
391--
469--
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
397-- @
474-- @
398
475
399-- $fairlyConcurrentNesting
476-- $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+-- @
401
502
402−-- $applicative
503+-- $folding
504+-- Folds
505+
506+-- $zipping
403-- Applicative composition
507-- Applicative composition
404
508
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+
405-- $reactive
542-- $reactive
406--
543--
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))
430--
567--
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