You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
11
7
12
8
# Example
13
9
14
10
```purescript
15
11
main = launchAff do
16
12
response <- Ajax.get "http://foo.bar"
17
-
liftEff $ log response.body
13
+
log response.body
18
14
```
19
15
20
-
See the [tests](https://github.com/slamdata/purescript-aff/blob/master/test/Test/Main.purs) for more examples.
21
-
22
16
# Getting Started
23
17
24
18
## Installation
@@ -38,125 +32,157 @@ deleteBlankLines path = do
38
32
saveFile path contents'
39
33
```
40
34
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.
42
38
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.
44
43
45
44
## Escaping Callback Hell
46
45
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!
48
48
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:
50
51
51
52
```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
53
54
```
54
55
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.
56
58
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`.
58
65
59
66
```javascript
60
-
exports.ajaxGet=function(callback) { // accepts a callback
61
-
returnfunction(request) { // and a request
62
-
returnfunction() { // 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
+
returnfunction (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
+
returnfunction (cancelError) {
79
+
returnfunction (cancelerError, cancelerSuccess) {
80
+
req.cancel(); // cancel the request
81
+
cancelerSuccess(); // invoke the success callback for the canceler
82
+
};
83
+
};
84
+
};
85
+
};
69
86
```
70
87
71
88
```purescript
72
-
foreign import ajaxGet :: forall e. (Response -> Eff e Unit) -> Request -> Eff e Unit
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:
83
101
84
102
```purescript
85
-
do response <- ajaxGet' req
86
-
liftEff $ log response.body
103
+
example = do
104
+
response <- ajaxGet req
105
+
log response.body
87
106
```
88
107
89
108
## Eff
90
109
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
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.
102
122
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.
107
126
108
127
## Dealing with Failure
109
128
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.
111
131
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.
113
133
114
-
1.**Attempt**
115
-
2.**Alt**
116
-
3.**MonadError**
134
+
1.**Alt**
135
+
2.**MonadError**
136
+
3.**Bracketing**
117
137
118
-
#### 1. Attempt
138
+
#### 1. Alt
119
139
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:
121
142
122
143
```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
124
147
```
125
148
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
132
150
133
-
#### 2. Alt
151
+
`Aff` has a `MonadError` instance, which comes with two functions:
152
+
`catchError`, and `throwError`.
134
153
135
-
Because `Aff` has an `Alt` instance, you may also use the operator `<|>` to provide an alternative computation in the event of failure:
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
140
164
```
141
165
142
-
#### 3. MonadError
143
-
144
-
`Aff` has a `MonadError` instance, which comes with two functions: `catchError`, and `throwError`.
166
+
#### 3. Bracketing
145
167
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.
148
171
149
172
```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)
153
178
```
154
179
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.
156
182
157
183
## Forking
158
184
159
-
Using the `forkAff`, you can "fork" an asynchronous computation, which means
185
+
Using `forkAff`, you can "fork" an asynchronous computation, which means
160
186
that its activities will not block the current thread of execution:
161
187
162
188
```purescript
@@ -167,66 +193,82 @@ Because Javascript is single-threaded, forking does not actually cause the
167
193
computation to be run in a separate thread. Forking just allows the subsequent
168
194
actions to execute without waiting for the forked computation to complete.
169
195
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.
172
200
173
201
```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")
177
209
```
178
210
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
-
```
185
211
186
212
## AVars
187
213
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.
189
217
190
218
```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)
197
226
```
198
227
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
-
202
228
## Parallel Execution
203
229
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.
207
231
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
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
221
264
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
223
267
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
+
```
225
271
226
272
# API Docs
227
273
228
274
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