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
Copy file name to clipboardExpand all lines: learn/eff/index.markdown
+41-39
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
---
2
2
title: Handling Native Effects with the Eff Monad
3
3
author: Phil Freeman
4
-
published: 2015-07-16
4
+
published: 2016-05-24
5
5
---
6
6
7
7
In this post, I'm going to talk about PureScript's hybrid approach to handling side-effects.
@@ -35,22 +35,22 @@ import Prelude
35
35
36
36
importControl.Monad.Eff
37
37
importControl.Monad.Eff.Random (random)
38
-
importControl.Monad.Eff.Console (print)
38
+
importControl.Monad.Eff.Console (logShow)
39
39
40
40
printRandom =do
41
41
n <- random
42
-
print n
42
+
logShow n
43
43
```
44
44
45
45
This example requires the [`purescript-console`](https://pursuit.purescript.org/packages/purescript-console/) and [`purescript-random`](https://pursuit.purescript.org/packages/purescript-random/) dependencies to be installed:
If you save this file as `RandomExample.purs`, you will be able to compile and run it using PSCi:
50
+
If you save this file as `src/RandomExample.purs`, you will be able to compile and run it using PSCi:
51
51
52
52
pulp psci
53
-
53
+
54
54
> import RandomExample
55
55
> printRandom
56
56
...
@@ -62,8 +62,9 @@ This program uses `do`-notation to combine two types of native effects provided
62
62
#### Extensible Records and Extensible Effects
63
63
64
64
We can inspect the type of `printRandom` by using the `:type command`
65
-
66
-
> :type RandomExample.main
65
+
66
+
> import RandomExample
67
+
> :type main
67
68
68
69
The type of `main` will be printed to the console. You should see a type which looks like this:
69
70
@@ -74,12 +75,12 @@ This type looks quite complicated, but is easily explained by analogy with PureS
74
75
Consider a simple function which uses extensible records:
75
76
76
77
```haskell
77
-
fullName person = person.firstName ++""++ person.lastName
78
+
fullName person = person.firstName <>""<> person.lastName
78
79
```
79
80
80
81
This function creates a full name string from an object containing `firstName` and `lastName` properties. If you find the type of this function in PSCi as before, you will see this:
81
82
82
-
forall t. { firstName :: String, lastName :: String | t } -> String
83
+
forall t. { firstName :: String, lastName :: String | t } -> String
83
84
84
85
The readable version of this type is "`fullName` takes an object with `firstName` and `lastName` properties _and any other properties_ and returns a `String`".
85
86
@@ -90,7 +91,7 @@ That is, `fullName` does not care if you pass an object with _more_ properties,
90
91
Phil Freeman
91
92
```
92
93
93
-
Similarly, the type of `printRandom` above can be interpreted as follows: "`printRandom` is a side-effecting computation, which can be run in any environment which supports random number generation and console IO, _and any other types of side effect_, and which yields a value of type `Unit`".
94
+
Similarly, the type of `printRandom` above can be interpreted as follows: "`printRandom` is an effectful computation, which can be run in any environment which supports random number generation and console IO, _and any other types of side effect_, and which yields a value of type `Unit`".
94
95
95
96
This is the origin of the name "extensible effects": we can always _extend_ the set of side-effects, as long as we can support the set of effects that we need.
96
97
@@ -106,21 +107,21 @@ which is _not_ the same as the type of `main`.
106
107
107
108
However, we can instantiate the polymorphic type variable in such a way that the types do match. If we choose `e1 ~ (console :: CONSOLE | e)`, then the two rows are equal, up to reordering.
108
109
109
-
Similarly, `print` has a type which can be instantiated to match the type of `printRandom`:
110
+
Similarly, `logShow` has a type which can be instantiated to match the type of `printRandom`:
110
111
111
-
forall a e2. (Show a) => a -> Eff (console :: CONSOLE | e2) Unit
112
+
forall a e2. Show a => a -> Eff (console :: CONSOLE | e2) Unit
112
113
113
114
This time we have to choose `e2 ~ random :: RANDOM | e`.
114
115
115
-
The key is that we don't have to give a type for `printRandom` in order to be able to find these substitutions. `psc` will find a most general type for `printRandom` given the polymorphic types of `random` and `print`.
116
+
The key is that we don't have to give a type for `printRandom` in order to be able to find these substitutions. `psc` will find a most general type for `printRandom` given the polymorphic types of `random` and `logShow`.
116
117
117
118
#### Aside: The Kind of Eff
118
119
119
120
Looking at the [source code](https://github.com/purescript/purescript-eff/blob/master/src/Control/Monad/Eff.purs), you will see the following definition for `Eff`:
120
121
121
122
```
122
123
foreign import Eff :: # ! -> * -> *
123
-
```
124
+
```
124
125
125
126
`*` is the usual kind of types, and `!` is the kind of effects. The `#` kind constructor is used to construct kinds for _rows_, i.e. unordered, labelled collections.
126
127
@@ -138,23 +139,25 @@ If we annotate the previous example with a _closed_ row of effects:
138
139
main::Eff (console::CONSOLE, random::RANDOM) Unit
139
140
main =do
140
141
n <- random
141
-
print n
142
+
logShow n
142
143
```
143
144
144
145
(note the lack of a type variable here), then we cannot accidentally include a subcomputation which makes use of a different type of effect. This is an advantage of `Eff` over Haskell's more coarsely-grained `IO` monad.
145
146
146
147
#### Handlers and Actions
147
148
148
-
Rows of effects can also appear on the left-hand side of a function arrow. This is what differentiates actions like `print` and `random` from effect _handlers_.
149
+
Rows of effects can also appear on the left-hand side of a function arrow. This is what differentiates actions like `logShow` and `random` from effect _handlers_.
149
150
150
151
While actions _add_ to the set of required effects, a handler `subtracts` effects from the set.
151
152
152
153
Consider `catchException` from the [`purescript-exceptions`](https://pursuit.purescript.org/packages/purescript-exceptions/) package:
153
154
154
155
```haskell
155
-
catchException::forallae. (Error->Effea) ->
156
-
Eff (err::EXCEPTION|e) a->
157
-
Effea
156
+
catchException
157
+
::forallae
158
+
. (Error->Effea)
159
+
->Eff (err::EXCEPTION|e) a
160
+
->Effea
158
161
```
159
162
160
163
Note that the typeof the effect on the right of the final function arrow requires _fewer_ effects than the effect to its left.Namely, `catchException` _removes_ the `EXCEPTION` effect from the set of required effects.
@@ -165,8 +168,8 @@ For example, we can write a piece of code which uses exceptions, then wrap that
165
168
166
169
`purescript-eff` also defines the handler `runPure`, which takes a computation with _no_ side-effects, and safely evaluates it as a pure value:
167
170
168
-
typePurea=foralle.Effea
169
-
171
+
typePurea=Eff()a
172
+
170
173
runPure::foralla.Purea->a
171
174
172
175
For example, we can define a version of the division function for which division by zero results in an exception:
@@ -175,12 +178,12 @@ For example, we can define a version of the division function for which division
divide::foralle.Int->Int->Eff (err::EXCEPTION|e) Int
182
185
divide _ 0= throw "Division by zero"
183
-
divide n m =return (n / m)
186
+
divide n m =pure (n / m)
184
187
```
185
188
186
189
If we have already defined this function, we can use the `runPure` and `catchException` handlers to define a version of `divide` which reports its errors using `Either`:
@@ -198,7 +201,7 @@ Note that _after_ we use `catchException` to remove the `EXCEPTION` effect, ther
198
201
199
202
New effects can be defined using `foreign import data` just as in the case of types.
200
203
201
-
Suppose we wanted to define an effect for incrementing a single shared global counter. We simply declare the kind of our new type constructor to be `!`:
204
+
Suppose we wanted to define an effect for incrementing a single shared global counter. We simply declare the kind of our new type constructor to be `!`:
The `psc` compiler has special support for the `Eff` monad. Ordinarily, a chain of monadic binds might result in poor performance when executed in `nodejs` or in the browser. However, the compiler can generate code for the `Eff` monad without explicit calls to the monadic bind function `>>=`.
246
+
The `psc` compiler has special support for the `Eff` monad. Ordinarily, a chain of monadic binds might result in poor performance when executed in Node or in the browser. However, the compiler can generate code for the `Eff` monad without explicit calls to the monadic bind function `>>=`.
244
247
245
248
Take the random number generation from the start of the post. If we compile this example without optimizations, we end up the following Javascript:
Copy file name to clipboardExpand all lines: learn/ffi/index.markdown
+10-23
Original file line number
Diff line number
Diff line change
@@ -19,6 +19,8 @@ Let's take the following simple module as an example:
19
19
```haskell
20
20
moduleTestwhere
21
21
22
+
importPrelude
23
+
22
24
gcd::Int->Int->Int
23
25
gcd n m | n ==0= m
24
26
gcd n m | m ==0= n
@@ -72,18 +74,6 @@ generates the following Javascript:
72
74
var example$prime =100;
73
75
```
74
76
75
-
This scheme also applies to names of infix operators:
76
-
77
-
```haskell
78
-
(%) a b =...
79
-
```
80
-
81
-
generates
82
-
83
-
```javascript
84
-
var$percent=function(a) { ... }
85
-
```
86
-
87
77
#### Calling Javascript from PureScript
88
78
89
79
Javascript values and functions can be used from PureScript by using the FFI. The problem becomes how to choose suitable types for values originating in Javascript.
@@ -94,22 +84,20 @@ The general rule regarding types is that you can enforce as little or as much ty
94
84
95
85
In PureScript, JavaScript code is wrapped using a _foreign module_. A foreign module is just a CommonJS module which is associated with a PureScript module. Foreign modules are required to adhere to certain conventions:
96
86
97
-
- The module must contain a comment of the form `// module ModuleName`, which associates the foreign module with its companion PureScript module.
87
+
- The name of the foreign module must be the same as its companion PureScript module, with its extension changed to `.js`. This associates the foreign module with the PureScript module.
98
88
- All exports must be of the form `exports.name = value;`, specified at the top level.
99
89
100
90
Here is an example, where we export a function which computes interest amounts from a foreign module:
101
91
102
92
```javascript
103
93
"use strict";
104
94
105
-
// module Interest
106
-
107
95
exports.calculateInterest=function(amount) {
108
96
return amount *0.1;
109
97
};
110
98
```
111
99
112
-
This file should be saved as `src/Interest.js`. The corresponding PureScript module `Interest` will be saved in `src/Interest.purs` (these filenames are merely conventions, but are used by certain tools, such as the Pulp build tool), and will look like this:
100
+
This file should be saved as `src/Interest.js`. The corresponding PureScript module `Interest` will be saved in `src/Interest.purs`, and will look like this:
113
101
114
102
```purescript
115
103
module Interest where
@@ -128,18 +116,18 @@ Suppose we wanted to modify our `calculateInterest` function to take a second ar
A correct `foreign import` declaration now should use a foreign type whose runtime representation correctly handles functions of multiple arguments. The `purescript-functions` package provides a collection of such types for function arities from 0 to 10:
124
+
A correct `foreign import` declaration now should use a foreign type whose runtime representation correctly handles functions of multiple arguments. The `purescript-functions` package provides a collection of such types for function arities from 0 to 10:
139
125
140
126
```purescript
141
127
module Interest where
142
128
129
+
import Data.Function (Fn2)
130
+
143
131
foreign import calculateInterest :: Fn2 Number Number Number
144
132
```
145
133
@@ -155,8 +143,6 @@ An alternative is to use curried functions in the native module, using multiple
155
143
```javascript
156
144
"use strict";
157
145
158
-
// module Interest
159
-
160
146
exports.calculateInterest=function(amount) {
161
147
returnfunction(months) {
162
148
return amount *Math.exp(0.1, months);
@@ -179,9 +165,10 @@ For example, let's write a simple PureScript function with a constrained type, a
The `Options` type here is based on the options record from Haskell's`aeson` library.For our purposes, the default options will work, but we need to turn on the `unwrapNewtypes` option, so that our `newtype` constructor gets ignored during serialization:
0 commit comments