Skip to content

Commit b17683e

Browse files
committed
Merge pull request #62 from garyb/monadpar
Implement MonadPar and MonadRace
2 parents 4ab4eb7 + 3d96c52 commit b17683e

File tree

11 files changed

+160
-144
lines changed

11 files changed

+160
-144
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/.*
22
!/.gitignore
33
!/.travis.yml
4-
/output/
5-
/node_modules/
64
/bower_components/
5+
/node_modules/
6+
/output/

.travis.yml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
language: node_js
2-
sudo: false
3-
node_js: 5
2+
dist: trusty
3+
sudo: required
4+
node_js: 6
45
install:
6+
- npm install -g bower
57
- npm install
6-
- npm install bower -g
7-
- bower install
88
script:
9-
- npm test
9+
- bower install --production
10+
- npm run -s build
11+
- bower install
12+
- npm -s test
1013
after_success:
1114
- >-
1215
test $TRAVIS_TAG &&
13-
node_modules/.bin/psc-publish > .pursuit.json &&
14-
curl -X POST http://pursuit.purescript.org/packages \
15-
-d @.pursuit.json \
16-
-H 'Accept: application/json' \
17-
-H "Authorization: token ${GITHUB_TOKEN}"
16+
echo $GITHUB_TOKEN | pulp login &&
17+
echo y | pulp publish --no-push

README.md

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@
66

77
An asynchronous effect monad for PureScript.
88

9-
The moral equivalent of `ErrorT (ContT Unit (Eff e) a`, for effects `e`.
9+
The moral equivalent of `ErrorT (ContT Unit (Eff e)) a`, for effects `e`.
1010

1111
`Aff` lets you say goodbye to monad transformers and callback hell!
1212

1313
# Example
1414

1515
```purescript
16-
main = launchAff $
17-
do response <- Ajax.get "http://foo.bar"
18-
liftEff $ log response.body
16+
main = launchAff do
17+
response <- Ajax.get "http://foo.bar"
18+
liftEff $ log response.body
1919
```
2020

2121
See the [tests](https://github.com/slamdata/purescript-aff/blob/master/test/Test/Main.purs) for more examples.
@@ -33,10 +33,10 @@ bower install purescript-aff
3333
An example of `Aff` is shown below:
3434

3535
```purescript
36-
deleteBlankLines path =
37-
do contents <- loadFile path
38-
let contents' = S.join "\n" $ A.filter (\a -> S.length a > 0) (S.split "\n" contents)
39-
saveFile path contents'
36+
deleteBlankLines path = do
37+
contents <- loadFile path
38+
let contents' = S.join "\n" $ A.filter (\a -> S.length a > 0) (S.split "\n" contents)
39+
saveFile path contents'
4040
```
4141

4242
This looks like ordinary, synchronous, imperative code, but actually operates asynchronously without any callbacks. Error handling is baked in so you only deal with it when you want to.
@@ -149,23 +149,23 @@ Here's an example of how you can use them:
149149

150150
```purescript
151151
do resp <- (Ajax.get "http://foo.com") `catchError` (const $ pure defaultResponse)
152-
if resp.statusCode != 200 then throwError myErr
152+
if resp.statusCode != 200 then throwError myErr
153153
else pure resp.body
154154
```
155155

156156
Thrown exceptions are propagated on the error channel, and can be recovered from using `attempt` or `catchError`.
157157

158158
## Forking
159159

160-
Using the `forkAff`, you can "fork" an asynchronous computation, which means
160+
Using the `forkAff`, you can "fork" an asynchronous computation, which means
161161
that its activities will not block the current thread of execution:
162162

163163
```purescript
164164
forkAff myAff
165165
```
166166

167-
Because Javascript is single-threaded, forking does not actually cause the
168-
computation to be run in a separate thread. Forking just allows the subsequent
167+
Because Javascript is single-threaded, forking does not actually cause the
168+
computation to be run in a separate thread. Forking just allows the subsequent
169169
actions to execute without waiting for the forked computation to complete.
170170

171171
If the asynchronous computation supports it, you can "kill" a forked computation
@@ -191,28 +191,34 @@ The `Control.Monad.Aff.AVar` module contains asynchronous variables, which are v
191191
```purescript
192192
do v <- makeVar
193193
forkAff (later $ putVar v 1.0)
194-
a <- takeVar v
194+
a <- takeVar v
195195
liftEff $ log ("Succeeded with " ++ show a)
196196
```
197197

198-
You can use these constructs as one-sided blocking queues, which suspend (if
198+
You can use these constructs as one-sided blocking queues, which suspend (if
199199
necessary) on `take` operations, or as asynchronous, empty-or-full variables.
200200

201201
## Parallel Execution
202202

203-
If you only need the power of `Applicative`, then instead of using the monadic `Aff`, you can use the `Par` newtype wrapper defined in `Control.Monad.Aff.Par`.
203+
There are `MonadPar` and `MonadRace` instances defined for `Aff`, allowing for parallel execution of `Aff` computations.
204204

205-
This provides parallel instances of `Apply` and `Alt`.
205+
There are two ways of taking advantage of these instances - directly through the `par` and `race` functions from these classes, or by using the `Parallel` newtype wrapper that enables parallel behaviours through the `Applicative` and `Alternative` operators.
206206

207-
In the following example, two Ajax requests are initiated simultaneously (rather than in sequence, as they would be for `Aff`):
207+
In the following example, using the newtype, two Ajax requests are initiated simultaneously (rather than in sequence, as they would be for `Aff`):
208208

209209
```purescript
210-
runPar (f <$> Par (Ajax.get "http://foo.com") <*> Par (Ajax.get "http://foo.com"))
210+
runParallel (f <$> parallel (Ajax.get "http://foo.com") <*> parallel (Ajax.get "http://foo.com"))
211211
```
212212

213-
The `(<|>)` operator of the `Alt` instance of `Par` allows you to race two asynchronous computations, and use whichever value comes back first (or the first error, if both err).
213+
And the equivalent using the `MonadPar` function directly:
214214

215-
The `runPar` function allows you to unwrap the `Aff` and return to normal monadic (sequential) composition.
215+
```purescript
216+
par f (Ajax.get "http://foo.com") (Ajax.get "http://foo.com")
217+
```
218+
219+
The `race` function from `MonadPar` or the `(<|>)` operator of the `Alt` instance of `Parallel` allows you to race two asynchronous computations, and use whichever value comes back first (or the first error, if both err).
220+
221+
The `runParallel` function allows you to unwrap the `Aff` and return to normal monadic (sequential) composition.
216222

217223
A parallel computation can be canceled if both of its individual components can be canceled.
218224

bower.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
"purescript-console": "^1.0.0",
2121
"purescript-exceptions": "^1.0.0",
2222
"purescript-functions": "^1.0.0",
23-
"purescript-transformers": "^1.0.0"
23+
"purescript-parallel": "^1.0.0",
24+
"purescript-transformers": "^1.0.0",
25+
"purescript-unsafe-coerce": "^1.0.0"
2426
},
2527
"devDependencies": {
2628
"purescript-partial": "^1.1.2"

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
"private": true,
33
"scripts": {
44
"clean": "rimraf output && rimraf .pulp-cache",
5-
"build": "pulp build",
5+
"build": "pulp build --censor-lib --strict",
66
"test": "pulp test"
77
},
88
"devDependencies": {
9-
"purescript": "^0.9.1-rc.1",
109
"pulp": "^9.0.0",
11-
"rimraf": "^2.5.2"
10+
"purescript-psa": "^0.3.9",
11+
"purescript": "^0.9.1",
12+
"rimraf": "^2.5.0"
1213
}
1314
}

src/Control/Monad/Aff.purs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,27 @@ module Control.Monad.Aff
2121
where
2222

2323
import Prelude
24+
2425
import Control.Alt (class Alt)
2526
import Control.Alternative (class Alternative)
27+
import Control.Monad.Aff.Internal (AVBox, AVar, _killVar, _putVar, _takeVar, _makeVar)
2628
import Control.Monad.Cont.Class (class MonadCont)
2729
import Control.Monad.Eff (Eff)
2830
import Control.Monad.Eff.Class (class MonadEff)
2931
import Control.Monad.Eff.Exception (Error, EXCEPTION, throwException, error)
3032
import Control.Monad.Error.Class (class MonadError, throwError)
3133
import Control.Monad.Rec.Class (class MonadRec)
3234
import Control.MonadPlus (class MonadZero, class MonadPlus)
35+
import Control.Parallel.Class (class MonadRace, class MonadPar)
3336
import Control.Plus (class Plus)
37+
3438
import Data.Either (Either(..), either, isLeft)
3539
import Data.Foldable (class Foldable, foldl)
3640
import Data.Function.Uncurried (Fn2, Fn3, runFn2, runFn3)
3741
import Data.Monoid (class Monoid, mempty)
3842

43+
import Unsafe.Coerce (unsafeCoerce)
44+
3945
-- | An asynchronous computation with effects `e`. The computation either
4046
-- | errors or produces a value of type `a`.
4147
-- |
@@ -54,7 +60,7 @@ type PureAff a = forall e. Aff e a
5460
newtype Canceler e = Canceler (Error -> Aff e Boolean)
5561

5662
-- | Unwraps the canceler function from the newtype that wraps it.
57-
cancel :: forall e. Canceler e -> Error -> Aff e Boolean
63+
cancel :: forall e. Canceler e -> Error -> Aff e Boolean
5864
cancel (Canceler f) = f
5965

6066
-- | This function allows you to attach a custom canceler to an asynchronous
@@ -202,6 +208,48 @@ instance semigroupCanceler :: Semigroup (Canceler e) where
202208
instance monoidCanceler :: Monoid (Canceler e) where
203209
mempty = Canceler (const (pure true))
204210

211+
instance monadParAff :: MonadPar (Aff e) where
212+
par f ma mb = do
213+
va <- makeVar
214+
vb <- makeVar
215+
c1 <- forkAff (putOrKill va =<< attempt ma)
216+
c2 <- forkAff (putOrKill vb =<< attempt mb)
217+
f <$> (takeVar va) <*> (takeVar vb)
218+
where
219+
putOrKill :: forall a. AVar a -> Either Error a -> Aff e Unit
220+
putOrKill v = either (killVar v) (putVar v)
221+
222+
instance monadRaceAff :: MonadRace (Aff e) where
223+
stall = throwError $ error "Stalled"
224+
race a1 a2 = do
225+
va <- makeVar -- the `a` value
226+
ve <- makeVar -- the error count (starts at 0)
227+
putVar ve 0
228+
c1 <- forkAff $ either (maybeKill va ve) (putVar va) =<< attempt a1
229+
c2 <- forkAff $ either (maybeKill va ve) (putVar va) =<< attempt a2
230+
takeVar va `cancelWith` (c1 <> c2)
231+
where
232+
maybeKill :: forall a. AVar a -> AVar Int -> Error -> Aff e Unit
233+
maybeKill va ve err = do
234+
e <- takeVar ve
235+
if e == 1 then killVar va err else pure unit
236+
putVar ve (e + 1)
237+
238+
makeVar :: forall e a. Aff e (AVar a)
239+
makeVar = fromAVBox $ _makeVar nonCanceler
240+
241+
takeVar :: forall e a. AVar a -> Aff e a
242+
takeVar q = fromAVBox $ runFn2 _takeVar nonCanceler q
243+
244+
putVar :: forall e a. AVar a -> a -> Aff e Unit
245+
putVar q a = fromAVBox $ runFn3 _putVar nonCanceler q a
246+
247+
killVar :: forall e a. AVar a -> Error -> Aff e Unit
248+
killVar q e = fromAVBox $ runFn3 _killVar nonCanceler q e
249+
250+
fromAVBox :: forall a e. AVBox a -> Aff e a
251+
fromAVBox = unsafeCoerce
252+
205253
foreign import _cancelWith :: forall e a. Fn3 (Canceler e) (Aff e a) (Canceler e) (Aff e a)
206254

207255
foreign import _setTimeout :: forall e a. Fn3 (Canceler e) Int (Aff e a) (Aff e a)

src/Control/Monad/Aff/AVar.purs

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
11
-- | A low-level primitive for building asynchronous code.
22
module Control.Monad.Aff.AVar
3-
( AffAVar()
4-
, AVar()
5-
, AVAR()
6-
, killVar
3+
( AffAVar
4+
, AVAR
75
, makeVar
86
, makeVar'
9-
, modifyVar
10-
, putVar
117
, takeVar
8+
, putVar
9+
, modifyVar
10+
, killVar
11+
, module Exports
1212
) where
1313

1414
import Prelude
1515

16-
import Control.Monad.Aff (Aff(), Canceler(), nonCanceler)
16+
import Control.Monad.Aff (Aff, nonCanceler)
17+
import Control.Monad.Aff.Internal (AVar) as Exports
18+
import Control.Monad.Aff.Internal (AVBox, AVar, _killVar, _putVar, _takeVar, _makeVar)
1719
import Control.Monad.Eff.Exception (Error())
1820

19-
import Data.Function.Uncurried (Fn2(), Fn3(), runFn2, runFn3)
21+
import Data.Function.Uncurried (runFn3, runFn2)
2022

21-
foreign import data AVAR :: !
23+
import Unsafe.Coerce (unsafeCoerce)
2224

23-
foreign import data AVar :: * -> *
25+
foreign import data AVAR :: !
2426

2527
type AffAVar e a = Aff (avar :: AVAR | e) a
2628

2729
-- | Makes a new asynchronous avar.
2830
makeVar :: forall e a. AffAVar e (AVar a)
29-
makeVar = _makeVar nonCanceler
31+
makeVar = fromAVBox $ _makeVar nonCanceler
3032

3133
-- | Makes a avar and sets it to some value.
3234
makeVar' :: forall e a. a -> AffAVar e (AVar a)
@@ -37,25 +39,20 @@ makeVar' a = do
3739

3840
-- | Takes the next value from the asynchronous avar.
3941
takeVar :: forall e a. AVar a -> AffAVar e a
40-
takeVar q = runFn2 _takeVar nonCanceler q
42+
takeVar q = fromAVBox $ runFn2 _takeVar nonCanceler q
4143

4244
-- | Puts a new value into the asynchronous avar. If the avar has
4345
-- | been killed, this will result in an error.
4446
putVar :: forall e a. AVar a -> a -> AffAVar e Unit
45-
putVar q a = runFn3 _putVar nonCanceler q a
47+
putVar q a = fromAVBox $ runFn3 _putVar nonCanceler q a
4648

4749
-- | Modifies the value at the head of the avar (will suspend until one is available).
4850
modifyVar :: forall e a. (a -> a) -> AVar a -> AffAVar e Unit
4951
modifyVar f v = takeVar v >>= (f >>> putVar v)
5052

5153
-- | Kills an asynchronous avar.
5254
killVar :: forall e a. AVar a -> Error -> AffAVar e Unit
53-
killVar q e = runFn3 _killVar nonCanceler q e
54-
55-
foreign import _makeVar :: forall e a. Canceler e -> AffAVar e (AVar a)
56-
57-
foreign import _takeVar :: forall e a. Fn2 (Canceler e) (AVar a) (AffAVar e a)
58-
59-
foreign import _putVar :: forall e a. Fn3 (Canceler e) (AVar a) a (AffAVar e Unit)
55+
killVar q e = fromAVBox $ runFn3 _killVar nonCanceler q e
6056

61-
foreign import _killVar :: forall e a. Fn3 (Canceler e) (AVar a) Error (AffAVar e Unit)
57+
fromAVBox :: forall a e. AVBox a -> AffAVar e a
58+
fromAVBox = unsafeCoerce
File renamed without changes.

src/Control/Monad/Aff/Internal.purs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module Control.Monad.Aff.Internal
2+
( AVBox
3+
, AVar
4+
, _makeVar
5+
, _takeVar
6+
, _putVar
7+
, _killVar
8+
) where
9+
10+
import Prelude
11+
12+
import Control.Monad.Eff.Exception (Error)
13+
14+
import Data.Function.Uncurried (Fn2, Fn3)
15+
16+
foreign import data AVar :: * -> *
17+
18+
foreign import data AVBox :: * -> *
19+
20+
foreign import _makeVar :: forall c a. c -> AVBox (AVar a)
21+
22+
foreign import _takeVar :: forall c a. Fn2 c (AVar a) (AVBox a)
23+
24+
foreign import _putVar :: forall c a. Fn3 c (AVar a) a (AVBox Unit)
25+
26+
foreign import _killVar :: forall c a. Fn3 c (AVar a) Error (AVBox Unit)

0 commit comments

Comments
 (0)