Skip to content

Commit 59fd52d

Browse files
committed
Update README
1 parent 200f2fe commit 59fd52d

File tree

1 file changed

+144
-102
lines changed

1 file changed

+144
-102
lines changed

README.md

Lines changed: 144 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,16 @@
33
[![Latest release](http://img.shields.io/github/release/slamdata/purescript-aff.svg)](https://github.com/slamdata/purescript-aff/releases)
44
[![Build status](https://travis-ci.org/slamdata/purescript-aff.svg?branch=master)](https://travis-ci.org/slamdata/purescript-aff)
55

6-
An asynchronous effect monad for PureScript.
7-
8-
The moral equivalent of `ErrorT (ContT Unit (Eff e)) a`, for effects `e`.
9-
10-
`Aff` lets you say goodbye to monad transformers and callback hell!
6+
An asynchronous effect monad and threading model for PureScript.
117

128
# Example
139

1410
```purescript
1511
main = launchAff do
1612
response <- Ajax.get "http://foo.bar"
17-
liftEff $ log response.body
13+
log response.body
1814
```
1915

20-
See the [tests](https://github.com/slamdata/purescript-aff/blob/master/test/Test/Main.purs) for more examples.
21-
2216
# Getting Started
2317

2418
## Installation
@@ -38,125 +32,157 @@ deleteBlankLines path = do
3832
saveFile path contents'
3933
```
4034

41-
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.
35+
This looks like ordinary, synchronous, imperative code, but actually operates
36+
asynchronously without any callbacks. Error handling is baked in so you only
37+
deal with it when you want to.
4238

43-
The library contains instances for `Semigroup`, `Monoid`, `Apply`, `Applicative`, `Bind`, `Monad`, `Alt`, `Plus`, `MonadPlus`, `MonadEff`, and `MonadError`. These instances allow you to compose asynchronous code as easily as `Eff`, as well as interop with existing `Eff` code.
39+
The library contains instances for `Semigroup`, `Monoid`, `Apply`,
40+
`Applicative`, `Bind`, `Monad`, `Alt`, `Plus`, `MonadEff`, and `MonadError`.
41+
These instances allow you to compose asynchronous code as easily as `Eff`, as
42+
well as interop with existing `Eff` code.
4443

4544
## Escaping Callback Hell
4645

47-
Hopefully, you're using libraries that already use the `Aff` type, so you don't even have to think about callbacks!
46+
Hopefully, you're using libraries that already use the `Aff` type, so you
47+
don't even have to think about callbacks!
4848

49-
If you're building your own library, or you have to interact with some native code that expects callbacks, then *purescript-aff* provides a `makeAff` function:
49+
If you're building your own library, then *purescript-aff* provides a
50+
`makeAff` function:
5051

5152
```purescript
52-
makeAff :: forall e a. ((Error -> Eff e Unit) -> (a -> Eff e Unit) -> Eff e Unit) -> Aff e a
53+
makeAff :: forall eff a. ((Either Error a -> Eff eff Unit) -> Eff eff (Canceler eff)) -> Aff eff a
5354
```
5455

55-
This function expects you to provide a handler, which should call a user-supplied error callback or success callback with the result of the asynchronous computation.
56+
This function expects you to provide a handler, which should call the
57+
supplied callback with the result of the asynchronous computation.
5658

57-
For example, let's say we have an AJAX request function that expects a callback:
59+
You should also return `Canceler`, which is just a cleanup effect. Since
60+
`Aff` threads may be killed, all asynchronous operations should provide a
61+
mechanism for unscheduling it.
62+
63+
*purescript-aff* also provides functions for easily binding FFI definitions in
64+
`Control.Monad.Aff.Compat`.
5865

5966
```javascript
60-
exports.ajaxGet = function(callback) { // accepts a callback
61-
return function(request) { // and a request
62-
return function() { // returns an effect
63-
doNativeRequest(request, function(response) {
64-
callback(response)(); // callback itself returns an effect
65-
});
66-
}
67-
}
68-
}
67+
exports._ajaxGet = function (request) { // accepts a request
68+
return function (onError, onSuccess) { // and callbacks
69+
var req = doNativeRequest(request, function (err, response) { // make the request
70+
if (err != null) {
71+
onError(err); // invoke the error callback in case of an error
72+
} else {
73+
onSuccess(response); // invoke the success callback with the reponse
74+
}
75+
});
76+
77+
// Return a canceler, which is just another Aff effect.
78+
return function (cancelError) {
79+
return function (cancelerError, cancelerSuccess) {
80+
req.cancel(); // cancel the request
81+
cancelerSuccess(); // invoke the success callback for the canceler
82+
};
83+
};
84+
};
85+
};
6986
```
7087

7188
```purescript
72-
foreign import ajaxGet :: forall e. (Response -> Eff e Unit) -> Request -> Eff e Unit
89+
foreign import _ajaxGet :: forall eff. Request -> EffFnAff (ajax :: AJAX | eff) Response
7390
```
7491

7592
We can wrap this into an asynchronous computation like so:
7693

7794
```purescript
78-
ajaxGet' :: forall e. Request -> Aff e Response
79-
ajaxGet' req = makeAff (\error success -> ajaxGet success req)
95+
ajaxGet :: forall eff. Request -> Aff (ajax :: AJAX | eff) Response
96+
ajaxGet = fromEffFnAff <<< _ajaxGet
8097
```
8198

82-
This eliminates callback hell and allows us to write code simply using `do` notation:
99+
This eliminates callback hell and allows us to write code simply using `do`
100+
notation:
83101

84102
```purescript
85-
do response <- ajaxGet' req
86-
liftEff $ log response.body
103+
example = do
104+
response <- ajaxGet req
105+
log response.body
87106
```
88107

89108
## Eff
90109

91-
All purely synchronous computations (`Eff`) can be lifted to asynchronous computations with `liftEff` defined in `Control.Monad.Eff.Class` (see [here](https://github.com/purescript/purescript-eff)).
110+
All purely synchronous computations (`Eff`) can be lifted to asynchronous
111+
computations with `liftEff` defined in `Control.Monad.Eff.Class` (see
112+
[here](https://github.com/purescript/purescript-eff)).
92113

93114
```purescript
94115
import Control.Monad.Eff.Class
95116
96117
liftEff $ log "Hello world!"
97118
```
98119

99-
This lets you write your whole program in `Aff`, and still call out to synchronous code.
100-
101-
If your `Eff` code throws exceptions (`err :: Exception`), you can remove the exceptions using `liftEff'`, which brings exceptions to the value level as an `Either Error a`:
120+
This lets you write your whole program in `Aff`, and still call out to
121+
synchronous code.
102122

103-
```purescript
104-
do e <- liftEff' myExcFunc
105-
liftEff $ either (const $ log "Oh noes!") (const $ log "Yays!") e
106-
```
123+
If your `Eff` code throws exceptions (`err :: Exception`), you can remove the
124+
exception label using `liftEff'`. Exceptions are part of `Aff`s built-in
125+
semantics, so they will always be caught and propagated anyway.
107126

108127
## Dealing with Failure
109128

110-
The `Aff` monad has error handling baked in, so ordinarily you don't have to worry about it.
129+
`Aff` has error handling baked in, so ordinarily you don't have to worry
130+
about it.
111131

112-
When you need to deal with failure, you have several options.
132+
When you need to deal with failure, you have a few options.
113133

114-
1. **Attempt**
115-
2. **Alt**
116-
3. **MonadError**
134+
1. **Alt**
135+
2. **MonadError**
136+
3. **Bracketing**
117137

118-
#### 1. Attempt
138+
#### 1. Alt
119139

120-
If you want to attempt a computation but recover from failure, you can use the `attempt` function:
140+
Because `Aff` has an `Alt` instance, you may also use the operator `<|>` to
141+
provide an alternative computation in the event of failure:
121142

122143
```purescript
123-
attempt :: forall e a. Aff e a -> Aff e (Either Error a)
144+
example = do
145+
result <- Ajax.get "http://foo.com" <|> Ajax.get "http://bar.com"
146+
pure result
124147
```
125148

126-
This returns an `Either Error a` that you can use to recover from failure.
127-
128-
```purescript
129-
do e <- attempt $ Ajax.get "http://foo.com"
130-
liftEff $ either (const $ log "Oh noes!") (const $ log "Yays!") e
131-
```
149+
#### 2. MonadError
132150

133-
#### 2. Alt
151+
`Aff` has a `MonadError` instance, which comes with two functions:
152+
`catchError`, and `throwError`.
134153

135-
Because `Aff` has an `Alt` instance, you may also use the operator `<|>` to provide an alternative computation in the event of failure:
154+
These are defined in
155+
[purescript-transformers](http://github.com/purescript/purescript-transformers).
156+
Here's an example of how you can use them:
136157

137158
```purescript
138-
do result <- Ajax.get "http://foo.com" <|> Ajax.get "http://bar.com"
139-
return result
159+
example = do
160+
resp <- Ajax.get "http://foo.com" `catchError` \_ -> pure defaultResponse
161+
when (resp.statusCode /= 200) do
162+
throwError myErr
163+
pure resp.body
140164
```
141165

142-
#### 3. MonadError
143-
144-
`Aff` has a `MonadError` instance, which comes with two functions: `catchError`, and `throwError`.
166+
#### 3. Bracketing
145167

146-
These are defined in [purescript-transformers](http://github.com/purescript/purescript-transformers).
147-
Here's an example of how you can use them:
168+
`Aff` threads can be cancelled, but sometimes we need to guarantee an action
169+
gets run even in the presence of exceptions or cancellation. Use `bracket` to
170+
acquire resources and clean them up.
148171

149172
```purescript
150-
do resp <- (Ajax.get "http://foo.com") `catchError` (const $ pure defaultResponse)
151-
if resp.statusCode != 200 then throwError myErr
152-
else pure resp.body
173+
example =
174+
bracket
175+
(openFile myFile)
176+
(\file -> closeFile file)
177+
(\file -> appendFile "hello" file)
153178
```
154179

155-
Thrown exceptions are propagated on the error channel, and can be recovered from using `attempt` or `catchError`.
180+
In this case, `closeFile` will always be called regardless of exceptions once
181+
`openFile` completes.
156182

157183
## Forking
158184

159-
Using the `forkAff`, you can "fork" an asynchronous computation, which means
185+
Using `forkAff`, you can "fork" an asynchronous computation, which means
160186
that its activities will not block the current thread of execution:
161187

162188
```purescript
@@ -167,66 +193,82 @@ Because Javascript is single-threaded, forking does not actually cause the
167193
computation to be run in a separate thread. Forking just allows the subsequent
168194
actions to execute without waiting for the forked computation to complete.
169195

170-
If the asynchronous computation supports it, you can "kill" a forked computation
171-
using the returned canceler:
196+
Forking returns a `Fiber eff a`, representing the deferred computation. You can
197+
kill a `Fiber` with `killFiber`, which will run any cancelers and cleanup, and
198+
you can observe a `Fiber`'s final value with `joinFiber`. If a `Fiber` threw
199+
an exception, it will be rethrown upon joining.
172200

173201
```purescript
174-
canceler <- forkAff myAff
175-
canceled <- canceler `cancel` (error "Just had to cancel")
176-
_ <- liftEff $ if canceled then (log "Canceled") else (log "Not Canceled")
202+
example = do
203+
fiber <- forkAff myAff
204+
killFiber (error "Just had to cancel") fiber
205+
result <- try (joinFiber fiber)
206+
if isLeft result
207+
then (log "Canceled")
208+
else (log "Not Canceled")
177209
```
178210

179-
If you want to run a custom canceler if some other asynchronous computation is
180-
cancelled, you can use the `cancelWith` combinator:
181-
182-
```purescript
183-
otherAff `cancelWith` myCanceler
184-
```
185211

186212
## AVars
187213

188-
The `Control.Monad.Aff.AVar` module contains asynchronous variables, which are very similar to Haskell's `MVar` construct. These can be used as low-level building blocks for asynchronous programs.
214+
The `Control.Monad.Aff.AVar` module contains asynchronous variables, which
215+
are very similar to Haskell's `MVar`. These can be used as low-level building
216+
blocks for asynchronous programs.
189217

190218
```purescript
191-
do v <- makeVar
192-
forkAff do
193-
delay (Milliseconds 50.0)
194-
putVar v 1.0
195-
a <- takeVar v
196-
liftEff $ log ("Succeeded with " ++ show a)
219+
example = d
220+
v <- makeEmptyVar
221+
_ <- forkAff do
222+
delay (Milliseconds 50.0)
223+
putVar v 1.0
224+
a <- takeVar v
225+
log ("Succeeded with " <> show a)
197226
```
198227

199-
You can use these constructs as one-sided blocking queues, which suspend (if
200-
necessary) on `take` operations, or as asynchronous, empty-or-full variables.
201-
202228
## Parallel Execution
203229

204-
There are `MonadPar` and `MonadRace` instances defined for `Aff`, allowing for parallel execution of `Aff` computations.
205-
206-
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.
230+
The `Parallel` instance for `Aff` makes writing parallel computations a breeze.
207231

208-
In the following example, using the newtype, two Ajax requests are initiated simultaneously (rather than in sequence, as they would be for `Aff`):
232+
Using `parallel` from `Control.Parallel` will turn a regular `Aff` into
233+
`ParAff`. `ParAff` has an `Applicative` instance which will run effects in
234+
parallel, and an `Alternative` instance which will race effects, returning the
235+
one which completes first (canceling the others). To get an `Aff` back, just
236+
run it with `sequential`.
209237

210238
```purescript
211-
runParallel (f <$> parallel (Ajax.get "http://foo.com") <*> parallel (Ajax.get "http://foo.com"))
239+
-- Make two requests in parallel
240+
example =
241+
sequential $
242+
Tuple <$> parallel (Ajax.get "https://foo.com")
243+
<*> parallel (Ajax.get "https://bar.com")
212244
```
213245

214-
And the equivalent using the `MonadPar` function directly:
215-
216246
```purescript
217-
par f (Ajax.get "http://foo.com") (Ajax.get "http://foo.com")
247+
-- Make a request with a 3 second timeout
248+
example =
249+
sequential $ oneOf
250+
[ parallel (Just <$> Ajax.get "https://foo.com")
251+
, parallel (Nothing <$ delay (Milliseconds 3000.0))
252+
]
218253
```
219254

220-
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).
255+
```purescript
256+
tvShows =
257+
[ "Stargate_SG-1"
258+
, "Battlestar_Galactics"
259+
, "Farscape"
260+
]
261+
262+
getPage page =
263+
Ajax.get $ "https://wikipedia.org/wiki/" <> page
221264
222-
The `runParallel` function allows you to unwrap the `Aff` and return to normal monadic (sequential) composition.
265+
-- Get all pages in parallel
266+
allPages = parTraverse getPage tvShows
223267
224-
A parallel computation can be canceled if both of its individual components can be canceled.
268+
-- Get the page that loads the fastest
269+
fastestPage = parOneOfMap getPage tvShows
270+
```
225271

226272
# API Docs
227273

228274
API documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-aff).
229-
230-
# See also
231-
232-
[A good overview of Aff](https://github.com/degoes-consulting/lambdaconf-2015/blob/master/speakers/jdegoes/async-purescript/presentation.pdf) was provided during LambdaConf 2015 conference

0 commit comments

Comments
 (0)