{-# OPTIONS_GHC -fno-warn-unused-imports #-} -- | -- Module : Asyncly.Tutorial -- Copyright : (c) 2017 Harendra Kumar -- -- License : BSD3 -- Maintainer : harendra.kumar@gmail.com -- -- Asyncly combines streaming, logic programming, concurrency and reactive -- programming under a single abstraction with a concise API, giving -- programmers a simple yet powerful tool for wide variety of applications. -- -- The 'AsyncT' monad operates on streams. Multiple streams of the same type -- can be summed using different sum style operators. For example, '<>' appends -- streams serially, '<=>' interleaves streams, '<|' combines streams in -- parallel and '<|>' combines streams in a fairly parallel manner. Fold -- utilities are provided to choose a fold style and an operator to fold a -- container in a desired manner producing an AsyncT stream. -- -- Streams of the same or different types can be zipped or transformed using -- applicative or monadic composition. The monadic composition is exactly the -- same as a list transformer except that it can be concurrent. It can be -- likened to nested loops in the imperative programming parlance. In addition -- to the regular monadic bind operator '>>=', other operators are provided to -- compose streams in alternate manners. For example, '>->' interleaves the -- iterations of a loop, '>>|' runs iterations in parallel and '>|>' runs all -- iterations in fairly parallel manner. -- -- With asyncly you can write concurrent and streaming applications without -- being aware of threads or synchronization. No explicit thread control is -- needed, where applicable the concurrency rate is automatically controlled -- based on the demand by the consumer. However, combinators are provided to -- fine tune the concurrency control. Streaming and concurrency allows -- expressing reactive applications conveniently. See 'Asyncly.Examples' for a -- simple SDL based FRP example. -- -- For examples and other ways to use the library see 'Asyncly.Examples' as -- well. -- module Asyncly.Tutorial ( -- * AsyncT Monad Transformer -- $transformer -- ** Generating Streams -- $generating -- ** Eliminating Streams -- $eliminating -- * Semigroup Style Composition -- $semigroup -- ** Serial composition (Append) -- $serial -- ** Interleaved composition -- $interleaved -- ** Parallel composition -- $parallel -- ** Fairly Parallel composition -- $fairParallel -- ** Custom composition -- $custom -- * Monoid Style composition -- $monoid -- * Transforming Streams -- $transforming -- ** Functor -- $functor -- ** Applicative -- $applicative -- ** Monad -- $listt -- ** Interleaved Iterations -- $interleavedNesting -- ** Concurrent Iterations -- $concurrentNesting -- ** Fairly Concurrent Iterations -- $fairlyConcurrentNesting -- * Folding Streams -- $folding -- * Zipping Streams -- $zipping -- * Concurrent Programming Examples -- $concurrent -- * Reactive Programming -- $reactive -- * State Machine Model -- $statemachine -- * Performance -- $performance -- * Interworking with Streaming Libraries -- $interwork -- * Comparison with Existing Packages -- $comparison ) where import Asyncly.Core import Asyncly.Prelude -- import Asyncly.Examples import Data.Semigroup import Control.Applicative import Control.Monad import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad.Trans.Class (MonadTrans (lift)) -- $transformer -- -- 'AsyncT' is a monad transformer, the type 'AsyncT m a' represents a stream -- of values of type 'a' in the underlying monad 'm'. In this section we -- describe how to create new values of type 'AsyncT' in the first place and -- how to eliminate or run a composed 'AsyncT' computation. Later sections -- discuss how to compose existing AsyncT streams or computations to create -- composite AsyncT streams. -- $generating -- -- Pure values can be placed into the stream type using 'return' or 'pure'. -- Effects in the IO monad can be lifted to the stream type using the 'liftIO' -- combinator. In a transformer stack we can lift actions from the lower monad -- using the 'lift' combinator. -- -- @ -- return 1 -- @ -- @ -- liftIO $ putStrLn "Hello world!" -- @ -- $eliminating -- -- 'runAsyncly' runs composed actions in the 'AsyncT' monad, lowering the type -- into the underlying monad and discarding the values generated by the stream; -- 'toList' collects the resulting stream in a list. 'uncons' can be used -- to deconstruct an 'AsyncT' stream into head and tail. -- -- @ -- import Asyncly -- -- main = do -- runAsyncly $ liftIO $ putStrLn "Hello world!" -- xs \<- toList $ return 1 <> return 2 -- print xs -- @ -- $semigroup -- AsyncT streams of the same type can be combined into a composite stream in -- many different ways using semigroup style binary composition operators -- ('<>', '<=>', '<|', '<|>', 'mplus'). -- We will use the following utility functions to illustrate these operators in -- the following sections. The @traced@ function prints the 'threadId' of the -- thread in which the computation runs and the @timer@ function adds a delay -- specified by the argument in seconds and then prints the argument. -- -- @ -- import Asyncly -- import Control.Concurrent -- -- traced m = liftIO (myThreadId >>= print) >> m -- -- timer n = liftIO $ do -- threadDelay (n * 1000000) -- tid \<- myThreadId -- putStrLn (show tid ++ ": Timer " ++ show n) -- @ -- $serial -- The '<>' operator composes multiple AsyncT streams in series i.e. the next -- event happens after the previous one is completed. The following example -- prints the sequence 3, 2, 1 and takes a total of 6 seconds: -- -- @ -- main = runAsyncly $ timer 3 <> timer 2 <> timer 1 -- @ -- @ -- ThreadId 36: Timer 3 -- ThreadId 36: Timer 2 -- ThreadId 36: Timer 1 -- @ -- $interleaved -- The '<=>' operator interleaves the two computations i.e. it yields one item -- from the first stream and then one item from the second stream. The -- following example should print the sequence 1, 3, 2, 4: -- -- @ -- main = runAsyncly $ (timer 1 <> timer 2) \<=> (timer 3 <> timer 4) -- @ -- @ -- ThreadId 36: Timer 1 -- ThreadId 36: Timer 3 -- ThreadId 36: Timer 2 -- ThreadId 36: Timer 4 -- @ -- $parallel -- -- The '<|' operator runs computations in parallel, preferring the first -- computation over the second. The second computation is run in parallel with -- the first only if the first computation is not producing enough output to -- keep the consumer busy otherwise the second computation is run serially -- after the first. We maintain a pool of minimum number of threads, for -- running the computations, that is big enough to match the pull rate of the -- consumer of the stream. -- Note that the left bias of the operator '<|' is suggested by its shape. -- It is also half cut '<|>' which is fairly parallel in contrast. -- -- The following example runs all the parallel computations in a single thread -- one after another, because none of them blocks. Note that if the consumer -- were faster than the producer this would start new threads for each -- computation to keep up even if none of them blocks: -- -- @ -- main = runAsyncly $ traced (sqrt 9) <| traced (sqrt 16) <| traced (sqrt 25) -- @ -- @ -- ThreadId 40 -- ThreadId 40 -- ThreadId 40 -- @ -- -- In the following example since the first computation blocks we start the -- next one in a separate thread and so on: -- -- @ -- main = runAsyncly $ timer 3 <| timer 2 <| timer 1 -- @ -- @ -- ThreadId 42: Timer 1 -- ThreadId 41: Timer 2 -- ThreadId 40: Timer 3 -- @ -- -- When we have a hierarchy of computations this operator follows DFS style: -- -- @ -- main = runAsyncly $ (p 1 <| p 2) <| (p 3 <| p 4) -- where p = liftIO . print -- @ -- @ -- 1 -- 2 -- 3 -- 4 -- @ -- -- Note that since this operator is left biased it should not be used when the -- computations have timers that are relative to each other because all -- computations may not be started at the same time and therefore timers in all -- of them will not start at the same time. When relative timing among all -- computations is important or when we need to start all computations at once -- for some reason use '<|>' instead. However, '<|' is useful in situations -- when we want to optimally utilize the resources and we know that the -- computations can run in parallel but we do not care if they actually run in -- parallel or not, that decision is left to the scheduler. Also, note that -- this operator can be used to fold infinite containers in contrast to '<|>', -- because it does not require us to run all of them at the same time. -- $fairParallel -- -- The 'Alternative' composition operator '<|>', like '<|', runs the composed -- computations in parallel. However, unlike '<|' it runs all of the -- computations in fairly parallel manner using a round robin scheduling. Note -- that this should not be used on infinite containers, as it will lead to an -- infinite scheduling queue. -- -- The following example sends a query to three search engines and prints the -- name of the search engine as a response arrives: -- -- @ -- import Asyncly -- import Network.HTTP.Simple -- -- main = runAsyncly $ google \<|> bing \<|> duckduckgo -- where -- google = get "https://www.google.com/search?q=haskell" -- bing = get "https://www.bing.com/search?q=haskell" -- duckduckgo = get "https://www.duckduckgo.com/?q=haskell" -- get s = liftIO (httpNoBody (parseRequest_ s) >> putStrLn (show s)) -- @ -- $custom -- -- The 'async' API can be used to create references to asynchronously running -- 'AsyncT' computations. We can then use 'uncons' to explore the -- streams arbitrarily and then recompose them to create a new 'AsyncT' stream. -- This way we can dynamically decide which stream to explore at any given time. -- Take an example of a merge sort of two sorted streams. We need to keep -- consuming items from the stream which has the lowest item in the sort order. -- This can be achieved using async references to streams. See -- 'Asyncly.Examples.MergeSortedStreams'. -- $monoid -- -- Containers can be folded using a suitable identity ('mempty', -- 'empty', 'mzero') and one of the binary operators mentioned earlier. Some -- fold utilities are provided by the library for convenience: -- -- * 'foldWith' folds a 'Foldable' container of AsyncT computations using the -- given function. -- * 'foldMapWith' folds like foldWith but also maps a function before folding. -- * 'forEachWith' is like foldMapwith but the container argument comes before -- the function argument. -- -- @ -- import Asyncly -- -- main = do -- runAsyncly $ liftIO $ putStrLn "Hello world!" -- xs \<- toList $ return 1 <> return 2; print xs -- xs \<- toList $ foldWith (<>) (map return [1..10]); print xs -- xs \<- toList $ foldMapWith (<>) return [1..10]; print xs -- xs \<- toList $ forEachWith (<>) [1..10] return; print xs -- @ -- $transforming -- -- The previous section discussed ways to compose streams without any -- transformation using sum style operators. In this section we will explore -- how to transform streams using 'Functor', 'Applicative' or 'Monad' -- style compositions. The applicative and monad composition of 'AsyncT' is -- exactly the same as a list transformer. -- For simplicity of illustration we are using streams of -- pure values in the following examples. However, the real application of -- AsyncT arises when these streams are generated using monadic actions. In the -- pure stream cases AsyncT is equivalent to the list monad. -- $functor -- -- The simplest way to transform an 'AsyncT' stream is by mapping a function on -- all elements of the stream using 'fmap'. -- -- @ -- import Asyncly -- -- main = do -- let nums = foldMapWith (<>) return [1..10] -- let strings = fmap show nums -- (toList $ fmap putStrLn strings) >>= sequence -- @ -- $applicative -- -- You can compose two or more streams using a function in 'Applicative' manner -- as well. In functional programmer's parlance the applicative implements -- non-determinism, exploring all possible combination of choices from both the -- streams. From an imperative programmer's point of view the applicative -- behaves like nested loops i.e. for each element in the first argument and -- for each element in the second argument apply the function. Note that the -- behavior of applicative instance of 'AsyncT' is exactly the same as that of -- a list transformer. -- -- The following example prints all combinations of size, color and shape: -- -- @ -- main = (toList $ (,,) \<$> sizes \<*> colors \<*> shapes) >>= print -- -- where -- -- toStream xs = foldMapWith (<>) return xs -- sizes = toStream [1, 2, 3] -- colors = toStream ["red", "green", "blue"] -- shapes = toStream ["triangle", "square", "circle"] -- @ -- $listt -- -- The monadic composition is similar to Applicative composition and just like -- a list transformer. It is like nested loops in the imperative parlance. The -- monadic continuation is performed for each element in a stream. -- -- For example the following program repeatedly reads strings from standard -- input and echoes them to the standard output. This is like one infinite -- loop: -- -- @ -- import Asyncly -- import Data.Semigroup (cycle1) -- -- main = runAsyncly $ do -- str <- cycle1 (liftIO getLine) -- liftIO $ putStrLn str -- @ -- -- This example is equivalent to three nested loops, the first one for sizes, -- the second one for colors and the third one for shapes. It prints all -- combinations of size, color and shape. -- -- @ -- main = runAsyncly $ do -- sz <- sizes -- cl <- colors -- sh <- shapes -- liftIO $ putStrLn $ show (sz, cl, sh) -- -- where -- -- toStream xs = foldMapWith (<>) return xs -- sizes = toStream [1, 2, 3] -- colors = toStream ["red", "green", "blue"] -- shapes = toStream ["triangle", "square", "circle"] -- @ -- -- $interleavedNesting -- -- Instead of the regular bind operation (>>=) we can use the interleaved bind -- operator (>->) to interleave the iterations of the nested loops. For -- example: -- -- @ -- import Asyncly -- -- main = runAsyncly $ -- sizes >-> \\sz -> -- colors >-> \\cl -> -- shapes >-> \\sh -> -- liftIO $ putStrLn $ show (sz, cl, sh) -- -- where -- -- toStream xs = foldMapWith (<>) return xs -- sizes = toStream [1, 2, 3] -- colors = toStream ["red", "green", "blue"] -- shapes = toStream ["triangle", "square", "circle"] -- @ -- $concurrentNesting -- -- @ -- import Asyncly -- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering)) -- import Control.Concurrent -- -- printWithTid s = do -- threadDelay 500 -- tid <- myThreadId -- putStrLn (show tid ++ ": " ++ s) -- -- main = runAsyncly $ do -- liftIO $ hSetBuffering stdout LineBuffering -- sizes >>| \\sz -> -- colors >>| \\cl -> -- shapes >>| \\sh -> -- liftIO $ printWithTid $ show (sz, cl, sh) -- -- where -- -- toStream xs = foldMapWith (<>) return xs -- sizes = toStream [1, 2, 3] -- colors = toStream ["red", "green", "blue"] -- shapes = toStream ["triangle", "square", "circle"] -- @ -- $fairlyConcurrentNesting -- -- @ -- import Asyncly -- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering)) -- import Control.Concurrent -- -- printWithTid s = do -- threadDelay 500 -- tid <- myThreadId -- putStrLn (show tid ++ ": " ++ s) -- -- main = runAsyncly $ do -- liftIO $ hSetBuffering stdout LineBuffering -- sizes >|> \\sz -> -- colors >|> \\cl -> -- shapes >|> \\sh -> -- liftIO $ printWithTid $ show (sz, cl, sh) -- -- where -- -- toStream xs = foldMapWith (<>) return xs -- sizes = toStream [1, 2, 3] -- colors = toStream ["red", "green", "blue"] -- shapes = toStream ["triangle", "square", "circle"] -- @ -- $folding -- Folds -- $zipping -- Applicative composition -- $concurrent -- -- There are two ways to achieve concurrency. We can generate individual -- elements of a stream concurrently by folding with parallel composition -- operators i.e. '<|' or '<|>'. 'forEachWith' can be useful in such cases. -- -- In the following example, we square each number concurrently but then -- sum and print them serially: -- -- @ -- import Asyncly -- import Data.List (sum) -- -- main = do -- squares \<- toList $ foldMapWith (<|) (\x -\> return $ x * x) [1..100] -- print $ sum squares -- @ -- -- The following example not just computes the squares concurrently but also -- computes the square root of their sums concurrently by using the parallel -- monadic bind. -- -- @ -- import Asyncly -- import Data.List (sum) -- -- main = do -- z \<- toList $ foldMapWith (\<|) (\\x -> return $ x * x) [1..100] >>| \\xsq -> -- foldMapWith (\<|) (\\x -> return $ x * x) [1..100] >>| \\ysq -> -- return $ sqrt (xsq + ysq) -- print $ sum z -- @ -- $reactive -- -- Let us see a reactive programming example: -- -- @ -- import Asyncly -- import Control.Concurrent (threadDelay) -- import Control.Monad (when) -- import Control.Monad.State -- import Data.Semigroup (cycle1) -- -- data Event = Harm Int | Heal Int | Quit deriving (Show) -- -- userAction :: MonadIO m => AsyncT m Event -- userAction = cycle1 $ liftIO askUser -- where -- askUser = do -- command <- getLine -- case command of -- "potion" -> return (Heal 10) -- "quit" -> return Quit -- _ -> putStrLn "What?" >> askUser -- -- acidRain :: MonadIO m => AsyncT m Event -- acidRain = cycle1 $ liftIO (threadDelay 1000000) >> return (Harm 1) -- -- game :: (MonadAsync m, MonadState Int m) => AsyncT m () -- game = do -- event \<- userAction \<|> acidRain -- case event of -- Harm n -> modify $ \h -> h - n -- Heal n -> modify $ \h -> h + n -- Quit -> fail "quit" -- -- h <- get -- when (h <= 0) $ fail "You die!" -- liftIO $ putStrLn $ "Health = " ++ show h -- -- main = do -- putStrLn "Your health is deteriorating due to acid rain,\ -- \ type \"potion\" or \"quit\"" -- runStateT (runAsyncly game) 60 -- @ -- $statemachine -- State machine stuff -- $comparison -- -- Even though asyncly covers all that is provided by the 'async' package or -- most of what is provided by the 'streaming', 'pipes' or 'conduit' packages, -- I would not say that it renders those useless. Asyncly is like monad if -- 'async' is applicative and monads and applicatives both have their use -- cases. It can completely repalce 'async', the ZipAsync type is equivalent to -- the functionality provided by 'async'. -- -- pipes and conduit are like Arrows and asyncly is like monad to them. You -- would use pipes and conduit when you do not need the product style -- composition and the implicit concurrency.