on master help sign in
Update doc (description and readme)
commite388e66d758b0870191906f339378ddb31339be7 authorHarendra Kumar <harendra.kumar@gmail.com> dateFri 20 Oct 2017, 09:36 (9 years ago) parent5bc84bd
4 files changed+358−138
README.md+39−11 asyncly.cabal+39−17 src/Asyncly.hs+51−110 src/Asyncly/Tutorial.hs+229
README.md+39−11
@@ -4,14 +4,42 @@
44[![Windows Build status](https://ci.appveyor.com/api/projects/status/h2bcgw7xa2jxe222?svg=true)](https://ci.appveyor.com/project/harendra-kumar/asyncly)
55[![Coverage Status](https://coveralls.io/repos/harendra-kumar/asyncly/badge.svg?branch=master&service=github)](https://coveralls.io/github/harendra-kumar/asyncly?branch=master)
66
7−Asyncly is best described as a superset of list transformer (`ListT`) or logic
8−programming monad (`LogicT`) with additional support for concurrent operation.
9−Monadic streams of data can be composed using serial or parallel compositions
10−with or without fair interleaving, enabling a high level composition of
11−concurrent tasks without requiring knowledge of low level concurrency
12−primitives. The programmer just expresses whether a task can run in parallel
13−with another. Threads, synchronization and concurrency rate control is handled
14−automatically.
15−
16−This library was originally inspired by the `transient` package authored by
17−Alberto G. Corona.
7+Asyncly is a general streaming and concurrent programming
8+monad allowing you to write a wide variety of applications ranging from
9+simple applications utilizing streaming IO to massively parallel concurrent
10+applications as well as reactive (FRP) applications with the same ease.
11+Asyncly provides high level concurrency primitives and hides the low level
12+concurrency details completely from the programmer. The programmer just
13+expresses whether a task can run in parallel with another. Threads,
14+synchronization and concurrency rate control are handled automatically. The
15+library has been written keeping high performance applications in mind.
16+
17+Asyncly subsumes all the use cases of list transformer (list-t) and logic
18+programming (logict) monads and adds concurrency on top. It is a streaming
19+library and therefore can be used for most of the streaming use cases as well
20+(i.e. where you would use conduit or pipes) with concurrency added. It is
21+also a first class reactive programming library and can be used where you
22+would use libraries like Yampa or reflex. One way to think about it is to
23+think of it as composable state machines with concurrency. You can write from
24+really simple to complex applications, from streaming to FRP or massively
25+concurrent applications using the same simple and concise abstractions.
26+
27+To get a taste of the simplicity and power of the APIs, here is a full
28+program to concurrently and recursively list the contents of a directory
29+tree:
30+
31+``` haskell
32+import Path.IO (listDir, getCurrentDir)
33+import Asyncly
34+
35+main = runAsyncly $ getCurrentDir >>= readdir
36+ where readdir d = do
37+ (dirs, files) <- lift $ listDir d
38+ liftIO $ mapM_ putStrLn $ map show files
39+ foldMapWith (<|>) readdir dirs
40+```
41+
42+See "Asyncly.Tutorial" and "Asyncly.Examples" for more details.
43+
44+This library was originally inspired by the `transient`
45+package authored by Alberto G. Corona.
asyncly.cabal+39−17
@@ -1,22 +1,43 @@
11name: asyncly
22version: 0.1.0
3−synopsis: Beautiful Concurrent, Reactive and Streaming Composition
4−description: Asyncly is best described as a superset of list transformer
5− (`ListT`) or logic programming monad (`LogicT`) with
6− additional support for concurrent operation. Concurrency
7− enables reactivity, providing beautiful composition of
8− stateful and reactive applications using monadic folds.
9− Monadic streams of data or events can be composed using
10− serial or parallel compositions with or without fair
11− interleaving, enabling a high level composition of
12− concurrent tasks without requiring knowledge of low level
13− concurrency primitives. The programmer just expresses
14− whether a task can run in parallel with another. Threads,
15− synchronization and concurrency rate control is handled
16− automatically.
17− .
18− This library was originally inspired by the `transient`
19− package authored by Alberto G. Corona.
3+synopsis: Beautiful Streaming, Concurrent and Reactive Composition
4+description: Asyncly is a general streaming and concurrent programming
5+ monad allowing you to write a wide variety of applications ranging from
6+ simple applications utilizing streaming IO to massively parallel concurrent
7+ applications as well as reactive (FRP) applications with the same ease.
8+ Asyncly provides high level concurrency primitives and hides the low level
9+ concurrency details completely from the programmer. The programmer just
10+ expresses whether a task can run in parallel with another. Threads,
11+ synchronization and concurrency rate control are handled automatically. The
12+ library has been written keeping high performance applications in mind.
13+ .
14+ Asyncly subsumes all the use cases of list transformer (list-t) and logic
15+ programming (logict) monads and adds concurrency on top. It is a streaming
16+ library and therefore can be used for most of the streaming use cases as well
17+ (i.e. where you would use conduit or pipes) with concurrency added. It is
18+ also a first class reactive programming library and can be used where you
19+ would use libraries like Yampa or reflex. One way to think about it is to
20+ think of it as composable state machines with concurrency. You can write from
21+ really simple to complex applications, from streaming to FRP or massively
22+ concurrent applications using the same simple and concise abstractions.
23+ .
24+ To get a taste of the simplicity and power of the APIs, here is a full
25+ program to concurrently and recursively list the contents of a directory
26+ tree:
27+ .
28+ > import Path.IO (listDir, getCurrentDir)
29+ > import Asyncly
30+ >
31+ > main = runAsyncly $ getCurrentDir >>= readdir
32+ > where readdir d = do
33+ > (dirs, files) <- lift $ listDir d
34+ > liftIO $ mapM_ putStrLn $ map show files
35+ > foldMapWith (<|>) readdir dirs
36+ .
37+ See "Asyncly.Tutorial" and "Asyncly.Examples" for more details.
38+ .
39+ This library was originally inspired by the `transient`
40+ package authored by Alberto G. Corona.
2041homepage: http://github.com/harendra-kumar/asyncly
2142bug-reports: https://github.com/harendra-kumar/asyncly/issues
2243license: BSD3
@@ -54,6 +75,7 @@ library
5475 , Asyncly.AsyncT
5576 , Asyncly.Prelude
5677 , Asyncly.Time
78+ , Asyncly.Tutorial
5779
5880 default-language: Haskell2010
5981 ghc-options: -Wall
src/Asyncly.hs+51−110
@@ -7,118 +7,59 @@
77-- Stability : experimental
88-- Portability : GHC
99--
10+-- 'AsyncT' is a monad transformer that extends the product style composition
11+-- of monads to streams; it is a functional porgramming equivalent to nested
12+-- loops in imperative programming. This style of composition allows composing
13+-- each element in one stream with each element in the other stream,
14+-- generalizing the monadic product of single elements. For example, you can
15+-- think of the IO Monad as a special case of the more general @AsyncT IO@
16+-- monad; with single element streams. List transformers and logic programming
17+-- monads also provide a similar product style composition of streams, however
18+-- 'AsyncT' generalizes it with the time dimension; allowing streams to be
19+-- composed in an asynchronous and concurrent fashion in many different ways.
20+--
21+-- The power of this seemingly simple addition of asynchronicity and
22+-- concurrency to product style streaming composition should not be
23+-- underestimated. It unifies a number of disparate abstractions into one
24+-- powerful and elegant abstraction. A wide variety of programming problems
25+-- can be solved elegantly with this abstraction. In particular, it unifies
26+-- three major programming domains namely non-deterministic (logic)
27+-- programming, concurrent programming and functional reactive programming. In
28+-- other words, you can do everything with this one abstraction that you could
29+-- with list transformers (e.g.
30+-- <https://hackage.haskell.org/package/list-t list-t>), logic programming
31+-- monads (e.g. <https://hackage.haskell.org/package/logict logict>),
32+-- streaming libraries (a lot of what
33+-- <https://hackage.haskell.org/package/conduit conduit> or
34+-- <https://hackage.haskell.org/package/pipes pipes> can do), concurrency
35+-- libraries (e.g. <https://hackage.haskell.org/package/async async>) and FRP
36+-- libraries (e.g. <https://hackage.haskell.org/package/Yampa Yampa> or
37+-- <https://hackage.haskell.org/package/reflex reflex>).
38+--
39+-- When it comes to streaming composition, if @conduit@ or @pipes@ are arrows
40+-- then asyncly is monad. Streaming libraries like conduit or pipes provide
41+-- a categorical composition (sum) of streams whereas asyncly provides a more
42+-- general monadic composition (product) of streams, with concurrency. Asyncly
43+-- interworks with major streaming libraries providing a way to use both types
44+-- of compositions and using the right tool at the right place.
45+-- When it comes to concurrency, asyncly may be comared with
46+-- imperative style concurrency frameworks like
47+-- <https://en.wikipedia.org/wiki/OpenMP OpenMP> or
48+-- <https://en.wikipedia.org/wiki/Cilk Cilk>.
49+--
50+-- For more details please see the 'Asyncly.Tutorial' and 'Asyncly.Examples'.
51+
52+-- A simple inline example here illustrating applicative, monad and alternative
53+-- compositions.
1054--
11−-- Asyncly allows expressing and composing state machines and event driven
12−-- programs in a straightforward manner. It allows waiting for events to occur
13−-- in a non-blocking manner and process them asynchronously; multiple events
14−-- can be processed in parallel. Tasks can be easily split into smaller tasks,
15−-- processed in parallel and results combined.
16−--
17−-- The 'Alternative' composition @(\<|\>)@ is used to express asynchronous or
18−-- parallel tasks. The following example demonstrates generation and printing
19−-- of random numbers happening in parallel:
20−--
21−-- @
22−-- import Control.Applicative ((\<|\>))
23−-- import Control.Concurrent (threadDelay)
24−-- import Control.Monad.IO.Class (liftIO)
25−-- import System.Random (randomIO)
26−-- import Asyncly
27−--
28−-- main = wait_ $ do
29−-- x <- loop
30−-- liftIO $ print x
31−--
32−-- where
33−--
34−-- loop = do
35−-- liftIO $ threadDelay 1000000
36−-- x <- liftIO (randomIO :: IO Int)
37−-- return x \<|\> loop
38−-- @
39−--
40−-- Here two random number generation loops run in parallel so two numbers are
41−-- printed every second. Note that the threadId printed for each is different:
42−--
43−-- @
44−-- import Control.Applicative ((\<|\>))
45−-- import Control.Concurrent (myThreadId, threadDelay)
46−-- import Control.Monad.IO.Class (liftIO)
47−-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
48−-- import System.Random (randomIO)
49−-- import Asyncly
50−--
51−-- main = wait_ $ do
52−-- liftIO $ hSetBuffering stdout LineBuffering
53−-- x <- loop \"A" \<|\> loop \"B"
54−-- liftIO $ myThreadId >>= putStr . show
55−-- >> putStr " "
56−-- >> print x
57−--
58−-- where
59−--
60−-- loop name = do
61−-- liftIO $ threadDelay 1000000
62−-- rnd <- liftIO (randomIO :: IO Int)
63−-- return (name, rnd) \<|\> loop name
64−-- @
65−-- Here the two loops are serially composed. For each value yielded by loop A,
66−-- loop B is executed. Four results are printed, all four run in separate
67−-- parallel threads. The composition is like ListT except that this is
68−-- concurrent:
69−--
70−-- @
71−-- import Control.Applicative ((\<|\>), empty)
72−-- import Control.Concurrent (myThreadId)
73−-- import Control.Monad.IO.Class (liftIO)
74−-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
75−-- import System.Random (randomIO)
76−-- import Asyncly
77−--
78−-- main = wait_ $ do
79−-- liftIO $ hSetBuffering stdout LineBuffering
80−-- x <- loop "A " 2
81−-- y <- loop "B " 2
82−-- liftIO $ myThreadId >>= putStr . show
83−-- >> putStr " "
84−-- >> print (x, y)
85−--
86−-- where
87−--
88−-- loop name n = do
89−-- rnd <- liftIO (randomIO :: IO Int)
90−-- let result = (name ++ show rnd)
91−-- repeat = if n > 1 then loop name (n - 1) else empty
92−-- in (return result) \<|\> repeat
93−-- @
94−--
95−-- Here we perform a simple multi-threaded map-reduce by squaring each number
96−-- in a separate thread and then summing the squares:
97−--
98−-- @
99−-- import Control.Applicative ((\<|\>), empty)
100−-- import Data.List (sum)
101−-- import Asyncly
102−--
103−-- main = do
104−-- squares <- wait $ do
105−-- x <- foldl (\<|\>) empty $ map return [1..100]
106−-- return (x * x)
107−-- print . sum $ squares
108−-- @
55+-- 'AsyncT' is like a list transformer ('ListT' or 'LogicT') that supports
56+-- concurrent composition. Lists or streams can be generated using serial or
57+-- concurrent monadic actions. A stream could be a stream of data or a stream
58+-- of asynchronous events in an event driven program. Streams can be appended
59+-- serially (<>) or interleaved fairly (\<=>), concurrent streams can be
60+-- unioned like sets (\<|) or interleaved fairly (\<|>). Streams can be
61+-- multiplied as in a list monad using the bind (>>=) operation.
10962--
110−-- 'Applicative' and 'Monoid' compositions work as expected.
111−
112−-- It can be thought of as a non-deterministic continuation monad or a
113−-- combination of ContT and ListT. It allows to capture the state of the
114−-- application at any point and trigger arbitrary number of continuations from
115−-- the capture point. Non-determinism or parallel continuations are introduced
116−-- using the <|> operator. It provides a convenient way to implement and
117−-- compose state machines without using callbacks. An event in the state
118−-- machine corresponds to a continuation. There are no cycles in the state
119−-- machine as each transition in the state machine is an independent instance
120−-- of the state machine. It is an immutable state machine!
121−
12263module Asyncly
12364 ( AsyncT
12465 , MonadAsync
src/Asyncly/Tutorial.hsnew file+229
@@ -0,0 +1,229 @@
1+-- |
2+-- Module : Asyncly.Tutorial
3+-- Copyright : (c) 2017 Harendra Kumar
4+--
5+-- License : BSD3
6+-- Maintainer : harendra.kumar@gmail.com
7+--
8+-- 'AsyncT' is like a list transformer ('ListT' or 'LogicT') that supports
9+-- concurrent composition. Lists or streams can be generated using serial or
10+-- concurrent monadic actions. A stream could be a stream of data or a stream
11+-- of asynchronous events in an event driven program. Streams can be appended
12+-- serially (<>) or interleaved fairly (\<=>), concurrent streams can be
13+-- unioned like sets (\<|) or interleaved fairly (\<|>). Streams can be
14+-- multiplied as in a list monad using the bind (>>=) operation.
15+--
16+-- Here is an example program that repeatedly reads strings from standard input
17+-- and echos them to the standard output:
18+--
19+-- @
20+-- import Asyncly
21+-- import Data.Semigroup (cycle1)
22+--
23+-- main = runAsyncly $ cycle1 (liftIO getLine) >>= liftIO . putStrLn
24+-- @
25+--
26+-- You can fold with (\<|) or (\<|>) to run actions concurrently. For
27+-- example to find the fastest search engine:
28+--
29+-- @
30+-- import Asyncly
31+-- import Network.HTTP.Simple
32+--
33+-- main = runAsyncly $ google \<|> bing \<|> duckduckgo
34+-- where
35+-- google = get "https://www.google.com/search?q=haskell"
36+-- bing = get "https://www.bing.com/search?q=haskell"
37+-- duckduckgo = get "https://www.duckduckgo.com/?q=haskell"
38+-- get s = liftIO (httpNoBody (parseRequest_ s) >> putStrLn (show s))
39+-- @
40+--
41+-- The above examples just used applicative or alternative compositions. They
42+-- can be done equivalently using async package as well. However the real power
43+-- of asyncly is in the monadic composition. Monadic composition allows
44+-- creating state machines or event networks or multiplying streams whatever
45+-- way you want to think about it. It is a very powerful general, concurrent
46+-- and reactive programming tool.
47+--
48+-- Let us now see an applicative example of the same:
49+--
50+-- Let's take an example of listing a directory tree:
51+--
52+-- @
53+-- import Path.IO (listDir, getCurrentDir)
54+-- import Asyncly
55+--
56+-- main = runAsyncly $ getCurrentDir >>= readdir
57+-- where readdir d = do
58+-- (dirs, files) <- lift $ listDir d
59+-- liftIO $ mapM_ putStrLn $ map show files
60+-- foldr (<>) mempty $ map readdir dirs
61+-- @
62+--
63+-- Just think that you had applicative and no monad in Haskell. We have
64+-- streaming libraries that are equivalent to applicative and asyncly is the
65+-- equivalent of the monadic composition of streams. We have had that in th
66+-- form of half hearted list transformers but asyncly provides a full fledged
67+-- concurent list transformer.
68+--
69+-- Let us see a reactive programming example:
70+--
71+-- @
72+-- import Asyncly
73+-- import Control.Concurrent (threadDelay)
74+-- import Control.Monad (when)
75+-- import Control.Monad.State
76+-- import Data.Semigroup (cycle1)
77+--
78+-- data Event = Harm Int | Heal Int | Quit deriving (Show)
79+--
80+-- userAction :: MonadIO m => AsyncT m Event
81+-- userAction = cycle1 $ liftIO askUser
82+-- where
83+-- askUser = do
84+-- command <- getLine
85+-- case command of
86+-- "potion" -> return (Heal 10)
87+-- "quit" -> return Quit
88+-- _ -> putStrLn "What?" >> askUser
89+--
90+-- acidRain :: MonadIO m => AsyncT m Event
91+-- acidRain = cycle1 $ liftIO (threadDelay 1000000) >> return (Harm 1)
92+--
93+-- game :: (MonadAsync m, MonadState Int m) => AsyncT m ()
94+-- game = do
95+-- event <- userAction <|> acidRain
96+-- case event of
97+-- Harm n -> modify $ \h -> h - n
98+-- Heal n -> modify $ \h -> h + n
99+-- Quit -> fail "quit"
100+--
101+-- h <- get
102+-- when (h <= 0) $ fail "You die!"
103+-- liftIO $ putStrLn $ "Health = " ++ show h
104+--
105+-- main = do
106+-- putStrLn "Your health is deteriorating due to acid rain,\
107+-- \ type \"potion\" or \"quit\""
108+-- runStateT (runAsyncly game) 60
109+-- @
110+--
111+-- You can create trees of demand driven parallel computations. Parallel
112+-- computations are started faster if the consumer is pulling faster. The
113+-- number of threads are automatically adjusted and are kept at a minimum
114+-- required depending on the consumer rate.
115+
116+-- Asyncly allows expressing and composing state machines and event driven
117+-- programs in a straightforward manner. It allows waiting for events to occur
118+-- in a non-blocking manner and process them asynchronously; multiple events
119+-- can be processed in parallel. Tasks can be easily split into smaller tasks,
120+-- processed in parallel and results combined.
121+--
122+-- The 'Alternative' composition @(\<|\>)@ is used to express asynchronous or
123+-- parallel tasks. The following example demonstrates generation and printing
124+-- of random numbers happening in parallel:
125+--
126+-- @
127+-- import Control.Applicative ((\<|\>))
128+-- import Control.Concurrent (threadDelay)
129+-- import Control.Monad.IO.Class (liftIO)
130+-- import System.Random (randomIO)
131+-- import Asyncly
132+--
133+-- main = wait_ $ do
134+-- x <- loop
135+-- liftIO $ print x
136+--
137+-- where
138+--
139+-- loop = do
140+-- liftIO $ threadDelay 1000000
141+-- x <- liftIO (randomIO :: IO Int)
142+-- return x \<|\> loop
143+-- @
144+--
145+-- Here two random number generation loops run in parallel so two numbers are
146+-- printed every second. Note that the threadId printed for each is different:
147+--
148+-- @
149+-- import Control.Applicative ((\<|\>))
150+-- import Control.Concurrent (myThreadId, threadDelay)
151+-- import Control.Monad.IO.Class (liftIO)
152+-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
153+-- import System.Random (randomIO)
154+-- import Asyncly
155+--
156+-- main = wait_ $ do
157+-- liftIO $ hSetBuffering stdout LineBuffering
158+-- x <- loop \"A" \<|\> loop \"B"
159+-- liftIO $ myThreadId >>= putStr . show
160+-- >> putStr " "
161+-- >> print x
162+--
163+-- where
164+--
165+-- loop name = do
166+-- liftIO $ threadDelay 1000000
167+-- rnd <- liftIO (randomIO :: IO Int)
168+-- return (name, rnd) \<|\> loop name
169+-- @
170+-- Here the two loops are serially composed. For each value yielded by loop A,
171+-- loop B is executed. Four results are printed, all four run in separate
172+-- parallel threads. The composition is like ListT except that this is
173+-- concurrent:
174+--
175+-- @
176+-- import Control.Applicative ((\<|\>), empty)
177+-- import Control.Concurrent (myThreadId)
178+-- import Control.Monad.IO.Class (liftIO)
179+-- import System.IO (stdout, hSetBuffering, BufferMode(LineBuffering))
180+-- import System.Random (randomIO)
181+-- import Asyncly
182+--
183+-- main = wait_ $ do
184+-- liftIO $ hSetBuffering stdout LineBuffering
185+-- x <- loop "A " 2
186+-- y <- loop "B " 2
187+-- liftIO $ myThreadId >>= putStr . show
188+-- >> putStr " "
189+-- >> print (x, y)
190+--
191+-- where
192+--
193+-- loop name n = do
194+-- rnd <- liftIO (randomIO :: IO Int)
195+-- let result = (name ++ show rnd)
196+-- repeat = if n > 1 then loop name (n - 1) else empty
197+-- in (return result) \<|\> repeat
198+-- @
199+--
200+-- Here we perform a simple multi-threaded map-reduce by squaring each number
201+-- in a separate thread and then summing the squares:
202+--
203+-- @
204+-- import Control.Applicative ((\<|\>), empty)
205+-- import Data.List (sum)
206+-- import Asyncly
207+--
208+-- main = do
209+-- squares <- wait $ do
210+-- x <- foldl (\<|\>) empty $ map return [1..100]
211+-- return (x * x)
212+-- print . sum $ squares
213+-- @
214+--
215+-- 'Applicative' and 'Monoid' compositions work as expected.
216+
217+-- It can be thought of as a non-deterministic continuation monad or a
218+-- combination of ContT and ListT. It allows to capture the state of the
219+-- application at any point and trigger arbitrary number of continuations from
220+-- the capture point. Non-determinism or parallel continuations are introduced
221+-- using the <|> operator. It provides a convenient way to implement and
222+-- compose state machines without using callbacks. An event in the state
223+-- machine corresponds to a continuation. There are no cycles in the state
224+-- machine as each transition in the state machine is an independent instance
225+-- of the state machine. It is an immutable state machine!
226+
227+module Asyncly.Tutorial
228+ ()
229+where