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,154 @@ 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`, `MonadError`, and
41
+
`Parallel`. These instances allow you to compose asynchronous code as easily
42
+
as `Eff`, as 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 you can make an `Aff` from
50
+
low-level `Eff` callbacks with `makeAff`.
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.
58
+
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.
56
62
57
-
For example, let's say we have an AJAX request function that expects a callback:
63
+
`Control.Monad.Aff.Compat` provides functions for easily binding FFI
64
+
definitions:
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`.
92
112
93
113
```purescript
94
-
import Control.Monad.Eff.Class
95
-
96
114
liftEff $ log "Hello world!"
97
115
```
98
116
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`:
117
+
This lets you write your whole program in `Aff`, and still call out to
118
+
synchronous code.
102
119
103
-
```purescript
104
-
do e <- liftEff' myExcFunc
105
-
liftEff $ either (const $ log "Oh noes!") (const $ log "Yays!") e
106
-
```
120
+
If your `Eff` code throws exceptions (`exception :: EXCEPTION`), you can
121
+
remove the exception label using `liftEff'`. Exceptions are part of `Aff`s
122
+
built-in semantics, so they will always be caught and propagated anyway.
107
123
108
124
## Dealing with Failure
109
125
110
-
The `Aff` monad has error handling baked in, so ordinarily you don't have to worry about it.
126
+
`Aff` has error handling baked in, so ordinarily you don't have to worry
127
+
about it.
111
128
112
-
When you need to deal with failure, you have several options.
129
+
When you need to deal with failure, you have a few options.
113
130
114
-
1.**Attempt**
115
-
2.**Alt**
116
-
3.**MonadError**
131
+
1.**Alt**
132
+
2.**MonadError**
133
+
3.**Bracketing**
117
134
118
-
#### 1. Attempt
135
+
#### 1. Alt
119
136
120
-
If you want to attempt a computation but recover from failure, you can use the `attempt` function:
137
+
Because `Aff` has an `Alt` instance, you may also use the operator `<|>` to
138
+
provide an alternative computation in the event of failure:
121
139
122
140
```purescript
123
-
attempt :: forall e a. Aff e a -> Aff e (Either Error a)
141
+
example = do
142
+
result <- Ajax.get "http://foo.com" <|> Ajax.get "http://bar.com"
143
+
pure result
124
144
```
125
145
126
-
This returns an `Either Error a` that you can use to recover from failure.
146
+
#### 2. MonadError
127
147
128
-
```purescript
129
-
do e <- attempt $ Ajax.get "http://foo.com"
130
-
liftEff $ either (const $ log "Oh noes!") (const $ log "Yays!") e
131
-
```
148
+
`Aff` has a `MonadError` instance, which comes with two functions:
149
+
`catchError`, and `throwError`.
132
150
133
-
#### 2. Alt
134
-
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
156
+
example = do
157
+
resp <- Ajax.get "http://foo.com" `catchError` \_ -> pure defaultResponse
158
+
when (resp.statusCode /= 200) do
159
+
throwError myErr
160
+
pure resp.body
140
161
```
141
162
142
-
#### 3. MonadError
163
+
#### 3. Bracketing
143
164
144
-
`Aff` has a `MonadError` instance, which comes with two functions: `catchError`, and `throwError`.
145
-
146
-
These are defined in [purescript-transformers](http://github.com/purescript/purescript-transformers).
147
-
Here's an example of how you can use them:
165
+
`Aff` threads can be cancelled, but sometimes we need to guarantee an action
166
+
gets run even in the presence of exceptions or cancellation. Use `bracket` to
167
+
acquire resources and clean them up.
148
168
149
169
```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
170
+
example =
171
+
bracket
172
+
(openFile myFile)
173
+
(\file -> closeFile file)
174
+
(\file -> appendFile "hello" file)
153
175
```
154
176
155
-
Thrown exceptions are propagated on the error channel, and can be recovered from using `attempt` or `catchError`.
177
+
In this case, `closeFile` will always be called regardless of exceptions once
178
+
`openFile` completes.
156
179
157
180
## Forking
158
181
159
-
Using the `forkAff`, you can "fork" an asynchronous computation, which means
182
+
Using `forkAff`, you can "fork" an asynchronous computation, which means
160
183
that its activities will not block the current thread of execution:
161
184
162
185
```purescript
@@ -167,66 +190,111 @@ Because Javascript is single-threaded, forking does not actually cause the
167
190
computation to be run in a separate thread. Forking just allows the subsequent
168
191
actions to execute without waiting for the forked computation to complete.
169
192
170
-
If the asynchronous computation supports it, you can "kill" a forked computation
171
-
using the returned canceler:
193
+
Forking returns a `Fiber eff a`, representing the deferred computation. You can
194
+
kill a `Fiber` with `killFiber`, which will run any cancelers and cleanup, and
195
+
you can observe a `Fiber`'s final value with `joinFiber`. If a `Fiber` threw
196
+
an exception, it will be rethrown upon joining.
172
197
173
198
```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")
199
+
example = do
200
+
fiber <- forkAff myAff
201
+
killFiber (error "Just had to cancel") fiber
202
+
result <- try (joinFiber fiber)
203
+
if isLeft result
204
+
then (log "Canceled")
205
+
else (log "Not Canceled")
177
206
```
178
207
179
-
If you want to run a custom canceler if some other asynchronous computation is
180
-
cancelled, you can use the `cancelWith` combinator:
208
+
209
+
## AVars
210
+
211
+
The `Control.Monad.Aff.AVar` module contains asynchronous variables, which
212
+
are very similar to Haskell's `MVar`.
213
+
214
+
`AVar`s represent a value that is either full or empty. Calling `takeVar` on
215
+
an empty `AVar` will queue until it is filled by a matching `putVar`.
181
216
182
217
```purescript
183
-
otherAff `cancelWith` myCanceler
218
+
example = do
219
+
var <- makeEmptyVar
220
+
_ <- forkAff do
221
+
value <- takeVar var
222
+
log $ "Got a value: " <> value
223
+
_ <- forkAff do
224
+
delay (Milliseconds 100.0)
225
+
putVar var "hello"
226
+
pure unit
227
+
```
228
+
```
229
+
(Waits 100ms)
230
+
> Got a value: hello
184
231
```
185
232
186
-
## AVars
187
-
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.
233
+
Likewise, calling `putVar` will queue until it is taken:
189
234
190
235
```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)
236
+
example = do
237
+
var <- makeEmptyVar
238
+
_ <- forkAff do
239
+
delay (Milliseconds 100.0)
240
+
value <- takeVar var
241
+
log $ "Got a value: " <> value
242
+
putVar var "hello"
243
+
log "Value taken"
244
+
```
245
+
```
246
+
(Waits 100ms)
247
+
> Value taken
248
+
> Got a value: hello
197
249
```
198
250
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.
251
+
These combinators (and a few more) can be used as the building blocks for
252
+
complex asynchronous coordination.
201
253
202
254
## Parallel Execution
203
255
204
-
There are `MonadPar` and `MonadRace` instances defined for `Aff`, allowing for parallel execution of `Aff` computations.
256
+
The `Parallel` instance for `Aff` makes writing parallel computations a breeze.
205
257
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.
207
-
208
-
In the following example, using the newtype, two Ajax requests are initiated simultaneously (rather than in sequence, as they would be for `Aff`):
258
+
Using `parallel` from `Control.Parallel` will turn a regular `Aff` into
259
+
`ParAff`. `ParAff` has an `Applicative` instance which will run effects in
260
+
parallel, and an `Alternative` instance which will race effects, returning the
261
+
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).
281
+
```purescript
282
+
tvShows =
283
+
[ "Stargate_SG-1"
284
+
, "Battlestar_Galactics"
285
+
, "Farscape"
286
+
]
221
287
222
-
The `runParallel` function allows you to unwrap the `Aff` and return to normal monadic (sequential) composition.
288
+
getPage page =
289
+
Ajax.get $ "https://wikipedia.org/wiki/" <> page
223
290
224
-
A parallel computation can be canceled if both of its individual components can be canceled.
291
+
-- Get all pages in parallel
292
+
allPages = parTraverse getPage tvShows
293
+
294
+
-- Get the page that loads the fastest
295
+
fastestPage = parOneOfMap getPage tvShows
296
+
```
225
297
226
298
# API Docs
227
299
228
300
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