1{-# OPTIONS_GHC -fno-warn-unused-imports #-}
3-- Module : Asyncly.Tutorial
4-- Copyright : (c) 2017 Harendra Kumar
7-- Maintainer : harendra.kumar@gmail.com
9-- Asyncly 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.
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.
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.
29-- With asyncly 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 'Asyncly.Examples' for a
35-- simple SDL based FRP example.
37-- For examples and other ways to use the library see 'Asyncly.Examples' as
40module Asyncly.Tutorial
42 -- * AsyncT Monad Transformer
45 -- ** Generating Streams
48 -- ** Eliminating Streams
51 -- * Semigroup Style Composition
54 -- ** Serial composition (Append)
57 -- ** Interleaved composition
60 -- ** Parallel composition
63 -- ** Fairly Parallel composition
66 -- ** Custom composition
69 -- * Monoid Style composition
72 -- * Transforming Streams
84 -- ** Interleaved Iterations
85 -- $interleavedNesting
87 -- ** Concurrent Iterations
90 -- ** Fairly Concurrent Iterations
91 -- $fairlyConcurrentNesting
99 -- * Concurrent Programming Examples
102 -- * Reactive Programming
105 -- * State Machine Model
111 -- * Interworking with Streaming Libraries
114 -- * Comparison with Existing Packages
120import Asyncly.Prelude
121-- import Asyncly.Examples
123import Control.Applicative
125import Control.Monad.IO.Class (MonadIO(..))
126import Control.Monad.Trans.Class (MonadTrans (lift))
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.
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.
148-- liftIO $ putStrLn "Hello world!"
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.
162-- runAsyncly $ liftIO $ putStrLn "Hello world!"
163-- xs \<- toList $ return 1 <> return 2
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.
178-- import Control.Concurrent
180-- traced m = liftIO (myThreadId >>= print) >> m
182-- timer n = liftIO $ do
183-- threadDelay (n * 1000000)
185-- putStrLn (show tid ++ ": Timer " ++ show n)
189-- The '<>' operator composes multiple AsyncT streams in series i.e. the next
190-- event happens after the previous one is completed. The following example
191-- prints the sequence 3, 2, 1 and takes a total of 6 seconds:
194-- main = runAsyncly $ timer 3 <> timer 2 <> timer 1
197-- ThreadId 36: Timer 3
198-- ThreadId 36: Timer 2
199-- ThreadId 36: Timer 1
203-- The '<=>' operator interleaves the two computations i.e. it yields one item
204-- from the first stream and then one item from the second stream. The
205-- following example should print the sequence 1, 3, 2, 4:
208-- main = runAsyncly $ (timer 1 <> timer 2) \<=> (timer 3 <> timer 4)
211-- ThreadId 36: Timer 1
212-- ThreadId 36: Timer 3
213-- ThreadId 36: Timer 2
214-- ThreadId 36: Timer 4
219-- The '<|' operator runs computations in parallel, preferring the first
220-- computation over the second. The second computation is run in parallel with
221-- the first only if the first computation is not producing enough output to
222-- keep the consumer busy otherwise the second computation is run serially
223-- after the first. We maintain a pool of minimum number of threads, for
224-- running the computations, that is big enough to match the pull rate of the
225-- consumer of the stream.
226-- Note that the left bias of the operator '<|' is suggested by its shape.
227-- It is also half cut '<|>' which is fairly parallel in contrast.
229-- The following example runs all the parallel computations in a single thread
230-- one after another, because none of them blocks. Note that if the consumer
231-- were faster than the producer this would start new threads for each
232-- computation to keep up even if none of them blocks:
235-- main = runAsyncly $ traced (sqrt 9) <| traced (sqrt 16) <| traced (sqrt 25)
243-- In the following example since the first computation blocks we start the
244-- next one in a separate thread and so on:
247-- main = runAsyncly $ timer 3 <| timer 2 <| timer 1
250-- ThreadId 42: Timer 1
251-- ThreadId 41: Timer 2
252-- ThreadId 40: Timer 3
255-- When we have a hierarchy of computations this operator follows DFS style:
258-- main = runAsyncly $ (p 1 <| p 2) <| (p 3 <| p 4)
259-- where p = liftIO . print
268-- Note that since this operator is left biased it should not be used when the
269-- computations have timers that are relative to each other because all
270-- computations may not be started at the same time and therefore timers in all
271-- of them will not start at the same time. When relative timing among all
272-- computations is important or when we need to start all computations at once
273-- for some reason use '<|>' instead. However, '<|' is useful in situations
274-- when we want to optimally utilize the resources and we know that the
275-- computations can run in parallel but we do not care if they actually run in
276-- parallel or not, that decision is left to the scheduler. Also, note that
277-- this operator can be used to fold infinite containers in contrast to '<|>',
278-- because it does not require us to run all of them at the same time.
282-- The 'Alternative' composition operator '<|>', like '<|', runs the composed
283-- computations in parallel. However, unlike '<|' it runs all of the
284-- computations in fairly parallel manner using a round robin scheduling. Note
285-- that this should not be used on infinite containers, as it will lead to an
286-- infinite scheduling queue.
288-- The following example sends a query to three search engines and prints the
289-- name of the search engine as a response arrives:
293-- import Network.HTTP.Simple
295-- main = runAsyncly $ google \<|> bing \<|> duckduckgo
297-- google = get "https://www.google.com/search?q=haskell"
298-- bing = get "https://www.bing.com/search?q=haskell"
299-- duckduckgo = get "https://www.duckduckgo.com/?q=haskell"
300-- get s = liftIO (httpNoBody (parseRequest_ s) >> putStrLn (show s))
305-- The 'async' API can be used to create references to asynchronously running
306-- 'AsyncT' computations. We can then use 'uncons' to explore the
307-- streams arbitrarily and then recompose them to create a new 'AsyncT' stream.
308-- This way we can dynamically decide which stream to explore at any given time.
309-- Take an example of a merge sort of two sorted streams. We need to keep
310-- consuming items from the stream which has the lowest item in the sort order.
311-- This can be achieved using async references to streams. See
312-- 'Asyncly.Examples.MergeSortedStreams'.
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:
320-- * 'foldWith' folds a 'Foldable' container of AsyncT computations using the
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.
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
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'.
358-- let nums = foldMapWith (<>) return [1..10]
359-- let strings = fmap show nums
360-- (toList $ fmap putStrLn strings) >>= sequence
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
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
399-- import Data.Semigroup (cycle1)
401-- main = runAsyncly $ do
402-- str <- cycle1 (liftIO getLine)
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
415-- liftIO $ putStrLn $ show (sz, cl, sh)
419-- toStream xs = foldMapWith (<>) return xs
420-- sizes = toStream [1, 2, 3]
421-- colors = toStream ["red", "green", "blue"]
422-- shapes = toStream ["triangle", "square", "circle"]
426-- $interleavedNesting
428-- Instead of the regular bind operation (>>=) we can use the interleaved bind
429-- operator (>->) to interleave the iterations of the nested loops. For
435-- main = runAsyncly $
439-- liftIO $ putStrLn $ show (sz, cl, sh)
443-- toStream xs = foldMapWith (<>) return xs
444-- sizes = toStream [1, 2, 3]
445-- colors = toStream ["red", "green", "blue"]
446-- shapes = toStream ["triangle", "square", "circle"]
453-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
454-- import Control.Concurrent
456-- printWithTid s = do
459-- putStrLn (show tid ++ ": " ++ s)
461-- main = runAsyncly $ do
462-- liftIO $ hSetBuffering stdout LineBuffering
466-- liftIO $ printWithTid $ show (sz, cl, sh)
470-- toStream xs = foldMapWith (<>) return xs
471-- sizes = toStream [1, 2, 3]
472-- colors = toStream ["red", "green", "blue"]
473-- shapes = toStream ["triangle", "square", "circle"]
476-- $fairlyConcurrentNesting
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
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"]
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)
544-- Let us see a reactive programming example:
548-- import Control.Concurrent (threadDelay)
549-- import Control.Monad (when)
550-- import Control.Monad.State
551-- import Data.Semigroup (cycle1)
553-- data Event = Harm Int | Heal Int | Quit deriving (Show)
555-- userAction :: MonadIO m => AsyncT m Event
556-- userAction = cycle1 $ liftIO askUser
561-- "potion" -> return (Heal 10)
562-- "quit" -> return Quit
563-- _ -> putStrLn "What?" >> askUser
565-- acidRain :: MonadIO m => AsyncT m Event
566-- acidRain = cycle1 $ liftIO (threadDelay 1000000) >> return (Harm 1)
568-- game :: (MonadAsync m, MonadState Int m) => AsyncT m ()
570-- event \<- userAction \<|> acidRain
572-- Harm n -> modify $ \h -> h - n
573-- Heal n -> modify $ \h -> h + n
574-- Quit -> fail "quit"
577-- when (h <= 0) $ fail "You die!"
578-- liftIO $ putStrLn $ "Health = " ++ show h
581-- putStrLn "Your health is deteriorating due to acid rain,\
582-- \ type \"potion\" or \"quit\""
583-- runStateT (runAsyncly game) 60
587-- State machine stuff
591-- Even though asyncly covers all that is provided by the 'async' package or
592-- most of what is provided by the 'streaming', 'pipes' or 'conduit' packages,
593-- I would not say that it renders those useless. Asyncly is like monad if
594-- 'async' is applicative and monads and applicatives both have their use
595-- cases. It can completely repalce 'async', the ZipAsync type is equivalent to
596-- the functionality provided by 'async'.
598-- pipes and conduit are like Arrows and asyncly is like monad to them. You
599-- would use pipes and conduit when you do not need the product style
600-- composition and the implicit concurrency.
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list