The Effect Monad
Chapter Goals
In the last chapter, we introduced applicative functors, an abstraction which we used to deal with sideeffects: optional values, error messages and validation. This chapter will introduce another abstraction for dealing with sideeffects in a more expressive way: monads.
The goal of this chapter is to explain why monads are a useful abstraction, and their connection with do notation.
Project Setup
The project adds the following dependencies:
effect
 defines theEffect
monad, the subject of the second half of the chapter. This dependency is often listed in every starter project (it's been a dependency of every chapter so far), so you'll rarely have to explicitly install it.random
 a monadic random number generator.reactbasichooks
 a web framework that we will use for our Address Book app.
Monads and Do Notation
Do notation was first introduced when we covered array comprehensions. Array comprehensions provide syntactic sugar for the concatMap
function from the Data.Array
module.
Consider the following example. Suppose we throw two dice and want to count the number of ways in which we can score a total of n
. We could do this using the following nondeterministic algorithm:
 Choose the value
x
of the first throw.  Choose the value
y
of the second throw.  If the sum of
x
andy
isn
then return the pair[x, y]
, else fail.
Array comprehensions allow us to write this nondeterministic algorithm in a natural way:
import Prelude
import Control.Plus (empty)
import Data.Array ((..))
countThrows :: Int > Array (Array Int)
countThrows n = do
x < 1 .. 6
y < 1 .. 6
if x + y == n
then pure [x, y]
else empty
We can see that this function works in PSCi:
> countThrows 10
[[4,6],[5,5],[6,4]]
> countThrows 12
[[6,6]]
In the last chapter, we formed an intuition for the Maybe
applicative functor, embedding PureScript functions into a larger programming language supporting optional values. In the same way, we can form an intuition for the array monad, embedding PureScript functions into a larger programming language supporting nondeterministic choice.
In general, a monad for some type constructor m
provides a way to use do notation with values of type m a
. Note that in the array comprehension above, every line contains a computation of type Array a
for some type a
. In general, every line of a do notation block will contain a computation of type m a
for some type a
and our monad m
. The monad m
must be the same on every line (i.e. we fix the sideeffect), but the types a
can differ (i.e. individual computations can have different result types).
Here is another example of do notation, this type applied to the type constructor Maybe
. Suppose we have some type XML
representing XML nodes, and a function
child :: XML > String > Maybe XML
which looks for a child element of a node, and returns Nothing
if no such element exists.
In this case, we can look for a deeplynested element by using do notation. Suppose we wanted to read a user's city from a user profile which had been encoded as an XML document:
userCity :: XML > Maybe XML
userCity root = do
prof < child root "profile"
addr < child prof "address"
city < child addr "city"
pure city
The userCity
function looks for a child element profile
, an element address
inside the profile
element, and finally an element city
inside the address
element. If any of these elements are missing, the return value will be Nothing
. Otherwise, the return value is constructed using Just
from the city
node.
Remember, the pure
function in the last line is defined for every Applicative
functor. Since pure
is defined as Just
for the Maybe
applicative functor, it would be equally valid to change the last line to Just city
.
The Monad Type Class
The Monad
type class is defined as follows:
class Apply m <= Bind m where
bind :: forall a b. m a > (a > m b) > m b
class (Applicative m, Bind m) <= Monad m
The key function here is bind
, defined in the Bind
type class. Just like for the <$>
and <*>
operators in the Functor
and Apply
type classes, the Prelude defines an infix alias >>=
for the bind
function.
The Monad
type class extends Bind
with the operations of the Applicative
type class that we have already seen.
It will be useful to see some examples of the Bind
type class. A sensible definition for Bind
on arrays can be given as follows:
instance bindArray :: Bind Array where
bind xs f = concatMap f xs
This explains the connection between array comprehensions and the concatMap
function that has been alluded to before.
Here is an implementation of Bind
for the Maybe
type constructor:
instance bindMaybe :: Bind Maybe where
bind Nothing _ = Nothing
bind (Just a) f = f a
This definition confirms the intuition that missing values are propagated through a do notation block.
Let's see how the Bind
type class is related to do notation. Consider a simple do notation block which starts by binding a value from the result of some computation:
do value < someComputation
whatToDoNext
Every time the PureScript compiler sees this pattern, it replaces the code with this:
bind someComputation \value > whatToDoNext
or, written infix:
someComputation >>= \value > whatToDoNext
The computation whatToDoNext
is allowed to depend on value
.
If there are multiple binds involved, this rule is applied multiple times, starting from the top. For example, the userCity
example that we saw earlier gets desugared as follows:
userCity :: XML > Maybe XML
userCity root =
child root "profile" >>= \prof >
child prof "address" >>= \addr >
child addr "city" >>= \city >
pure city
It is worth noting that code expressed using do notation is often much clearer than the equivalent code using the >>=
operator. However, writing binds explicitly using >>=
can often lead to opportunities to write code in pointfree form  but the usual warnings about readability apply.
Monad Laws
The Monad
type class comes equipped with three laws, called the monad laws. These tell us what we can expect from sensible implementations of the Monad
type class.
It is simplest to explain these laws using do notation.
Identity Laws
The rightidentity law is the simplest of the three laws. It tells us that we can eliminate a call to pure
if it is the last expression in a do notation block:
do
x < expr
pure x
The rightidentity law says that this is equivalent to just expr
.
The leftidentity law states that we can eliminate a call to pure
if it is the first expression in a do notation block:
do
x < pure y
next
This code is equivalent to next
, after the name x
has been replaced with the expression y
.
The last law is the associativity law. It tells us how to deal with nested do notation blocks. It states that the following piece of code:
c1 = do
y < do
x < m1
m2
m3
is equivalent to this code:
c2 = do
x < m1
y < m2
m3
Each of these computations involves three monadic expression m1
, m2
and m3
. In each case, the result of m1
is eventually bound to the name x
, and the result of m2
is bound to the name y
.
In c1
, the two expressions m1
and m2
are grouped into their own do notation block.
In c2
, all three expressions m1
, m2
and m3
appear in the same do notation block.
The associativity law tells us that it is safe to simplify nested do notation blocks in this way.
Note that by the definition of how do notation gets desugared into calls to bind
, both of c1
and c2
are also equivalent to this code:
c3 = do
x < m1
do
y < m2
m3
Folding With Monads
As an example of working with monads abstractly, this section will present a function which works with any type constructor in the Monad
type class. This should serve to solidify the intuition that monadic code corresponds to programming "in a larger language" with sideeffects, and also illustrate the generality which programming with monads brings.
The function we will write is called foldM
. It generalizes the foldl
function that we met earlier to a monadic context. Here is its type signature:
foldM :: forall m a b
. Monad m
=> (a > b > m a)
> a
> List b
> m a
Notice that this is the same as the type of foldl
, except for the appearance of the monad m
:
foldl :: forall a b
. (a > b > a)
> a
> List b
> a
Intuitively, foldM
performs a fold over a list in some context supporting some set of sideeffects.
For example, if we picked m
to be Maybe
, then our fold would be allowed to fail by returning Nothing
at any stage  every step returns an optional result, and the result of the fold is therefore also optional.
If we picked m
to be the Array
type constructor, then every step of the fold would be allowed to return zero or more results, and the fold would proceed to the next step independently for each result. At the end, the set of results would consist of all folds over all possible paths. This corresponds to a traversal of a graph!
To write foldM
, we can simply break the input list into cases.
If the list is empty, then to produce the result of type a
, we only have one option: we have to return the second argument:
foldM _ a Nil = pure a
Note that we have to use pure
to lift a
into the monad m
.
What if the list is nonempty? In that case, we have a value of type a
, a value of type b
, and a function of type a > b > m a
. If we apply the function, we obtain a monadic result of type m a
. We can bind the result of this computation with a backwards arrow <
.
It only remains to recurse on the tail of the list. The implementation is simple:
foldM f a (b : bs) = do
a' < f a b
foldM f a' bs
Note that this implementation is almost identical to that of foldl
on lists, with the exception of do notation.
We can define and test this function in PSCi. Here is an example  suppose we defined a "safe division" function on integers, which tested for division by zero and used the Maybe
type constructor to indicate failure:
safeDivide :: Int > Int > Maybe Int
safeDivide _ 0 = Nothing
safeDivide a b = Just (a / b)
Then we can use foldM
to express iterated safe division:
> import Data.List
> foldM safeDivide 100 (fromFoldable [5, 2, 2])
(Just 5)
> foldM safeDivide 100 (fromFoldable [2, 0, 4])
Nothing
The foldM safeDivide
function returns Nothing
if a division by zero was attempted at any point. Otherwise it returns the result of repeatedly dividing the accumulator, wrapped in the Just
constructor.
Monads and Applicatives
Every instance of the Monad
type class is also an instance of the Applicative
type class, by virtue of the superclass relationship between the two classes.
However, there is also an implementation of the Applicative
type class which comes "for free" for any instance of Monad
, given by the ap
function:
ap :: forall m a b. Monad m => m (a > b) > m a > m b
ap mf ma = do
f < mf
a < ma
pure (f a)
If m
is a lawabiding member of the Monad
type class, then there is a valid Applicative
instance for m
given by ap
.
The interested reader can check that ap
agrees with apply
for the monads we have already encountered: Array
, Maybe
and Either e
.
If every monad is also an applicative functor, then we should be able to apply our intuition for applicative functors to every monad. In particular, we can reasonably expect a monad to correspond, in some sense, to programming "in a larger language" augmented with some set of additional sideeffects. We should be able to lift functions of arbitrary arities, using map
and apply
, into this new language.
But monads allow us to do more than we could do with just applicative functors, and the key difference is highlighted by the syntax of do notation. Consider the userCity
example again, in which we looked for a user's city in an XML document which encoded their user profile:
userCity :: XML > Maybe XML
userCity root = do
prof < child root "profile"
addr < child prof "address"
city < child addr "city"
pure city
Do notation allows the second computation to depend on the result prof
of the first, and the third computation to depend on the result addr
of the second, and so on. This dependence on previous values is not possible using only the interface of the Applicative
type class.
Try writing userCity
using only pure
and apply
: you will see that it is impossible. Applicative functors only allow us to lift function arguments which are independent of each other, but monads allow us to write computations which involve more interesting data dependencies.
In the last chapter, we saw that the Applicative
type class can be used to express parallelism. This was precisely because the function arguments being lifted were independent of one another. Since the Monad
type class allows computations to depend on the results of previous computations, the same does not apply  a monad has to combine its sideeffects in sequence.
Exercises

(Easy) Look up the types of the
head
andtail
functions from theData.Array
module in thearrays
package. Use do notation with theMaybe
monad to combine these functions into a functionthird
which returns the third element of an array with three or more elements. Your function should return an appropriateMaybe
type. 
(Medium) Write a function
sums
which usesfoldM
to determine all possible totals that could be made using a set of coins. The coins will be specified as an array which contains the value of each coin. Your function should have the following result:> sums [] [0] > sums [1, 2, 10] [0,1,2,3,10,11,12,13]
Hint: This function can be written as a oneliner using
foldM
. You might want to use thenub
andsort
functions to remove duplicates and sort the result respectively. 
(Medium) Confirm that the
ap
function and theapply
operator agree for theMaybe
monad. 
(Medium) Verify that the monad laws hold for the
Monad
instance for theMaybe
type, as defined in themaybe
package. 
(Medium) Write a function
filterM
which generalizes thefilter
function on lists. Your function should have the following type signature:filterM :: forall m a. Monad m => (a > m Boolean) > List a > m (List a)
Test your function in PSCi using the
Maybe
andArray
monads. 
(Difficult) Every monad has a default
Functor
instance given by:map f a = do x < a pure (f x)
Use the monad laws to prove that for any monad, the following holds:
lift2 f (pure a) (pure b) = pure (f a b)
where the
Applicative
instance uses theap
function defined above. Recall thatlift2
was defined as follows:lift2 :: forall f a b c. Applicative f => (a > b > c) > f a > f b > f c lift2 f a b = f <$> a <*> b
Native Effects
We will now look at one particular monad which is of central importance in PureScript  the Effect
monad.
The Effect
monad is defined in the Effect
module. It is used to manage socalled native sideeffects. If you are familiar with Haskell, it is the equivalent of the IO
monad.
What are native sideeffects? They are the sideeffects which distinguish JavaScript expressions from idiomatic PureScript expressions, which typically are free from sideeffects. Some examples of native effects are:
 Console IO
 Random number generation
 Exceptions
 Reading/writing mutable state
And in the browser:
 DOM manipulation
 XMLHttpRequest / AJAX calls
 Interacting with a websocket
 Writing/reading to/from local storage
We have already seen plenty of examples of "nonnative" sideeffects:
 Optional values, as represented by the
Maybe
data type  Errors, as represented by the
Either
data type  Multifunctions, as represented by arrays or lists
Note that the distinction is subtle. It is true, for example, that an error message is a possible sideeffect of a JavaScript expression, in the form of an exception. In that sense, exceptions do represent native sideeffects, and it is possible to represent them using Effect
. However, error messages implemented using Either
are not a sideeffect of the JavaScript runtime, and so it is not appropriate to implement error messages in that style using Effect
. So it is not the effect itself which is native, but rather how it is implemented at runtime.
SideEffects and Purity
In a pure language like PureScript, one question which presents itself is: without sideeffects, how can one write useful realworld code?
The answer is that PureScript does not aim to eliminate sideeffects. It aims to represent sideeffects in such a way that pure computations can be distinguished from computations with sideeffects in the type system. In this sense, the language is still pure.
Values with sideeffects have different types from pure values. As such, it is not possible to pass a sideeffecting argument to a function, for example, and have sideeffects performed unexpectedly.
The only way in which sideeffects managed by the Effect
monad will be presented is to run a computation of type Effect a
from JavaScript.
The Spago build tool (and other tools) provide a shortcut, by generating additional JavaScript to invoke the main
computation when the application starts. main
is required to be a computation in the Effect
monad.
The Effect Monad
The goal of the Effect
monad is to provide a welltyped API for computations with sideeffects, while at the same time generating efficient JavaScript.
Here is an example. It uses the random
package, which defines functions for generating random numbers:
module Main where
import Prelude
import Effect (Effect)
import Effect.Random (random)
import Effect.Console (logShow)
main :: Effect Unit
main = do
n < random
logShow n
If this file is saved as src/Main.purs
, then it can be compiled and run using Spago:
$ spago run
Running this command, you will see a randomly chosen number between 0
and 1
printed to the console.
This program uses do notation to combine two native effects provided by the JavaScript runtime: random number generation and console IO.
As mentioned previously, the Effect
monad is of central importance to PureScript. The reason why it's central is because it is the conventional way to interoperate with PureScript's Foreign Function Interface
, which provides the mechanism to execute a program and perform side effects. While it's desireable to avoid using the Foreign Function Interface
, it's fairly critical to understand how it works and how to use it, so I recommend reading that chapter before doing any serious PureScript work. That said, the Effect
monad is fairly simple. It has a few helper functions, but aside from that it doesn't do much except encapsulate side effects.
Exceptions
Let's examine a function from the nodefs
package that involves two native side effects: reading mutable state, and exceptions:
readTextFile :: Encoding → String → Effect String
If we attempt to read a file that does not exist:
import Node.Encoding (Encoding(..))
import Node.FS.Sync (readTextFile)
main :: Effect Unit
main = do
lines < readTextFile UTF8 "iDoNotExist.md"
log lines
We encounter the following exception:
throw err;
^
Error: ENOENT: no such file or directory, open 'iDoNotExist.md'
...
errno: 2,
syscall: 'open',
code: 'ENOENT',
path: 'iDoNotExist.md'
To manage this exception gracefully, we can wrap the potentially problematic code in try
to handle either outcome:
main :: Effect Unit
main = do
result < try $ readTextFile UTF8 "iDoNotExist.md"
case result of
Right lines > log $ "Contents: \n" <> lines
Left error > log $ "Couldn't open file. Error was: " <> message error
try
runs an Effect
and returns eventual exceptions as a Left
value. If the computation succeeds, the result gets wrapped in a Right
:
try :: forall a. Effect a > Effect (Either Error a)
We can also generate our own exceptions. Here is an alternative implementation of Data.List.head
which throws an exception if the list is empty, rather than returing a Maybe
value of Nothing
.
exceptionHead :: List Int > Effect Int
exceptionHead l = case l of
x : _ > pure x
Nil > throwException $ error "empty list"
That was a somewhat impractical example, as it is usually better to avoid generating exceptions in PureScript code instead and use nonnative effects such as Either
and Maybe
to manage errors and missing values.
Mutable State
There is another effect defined in the core libraries: the ST
effect.
The ST
effect is used to manipulate mutable state. As pure functional programmers, we know that shared mutable state can be problematic. However, the ST
effect uses the type system to restrict sharing in such a way that only safe local mutation is allowed.
The ST
effect is defined in the Control.Monad.ST
module. To see how it works, we need to look at the types of its actions:
new :: forall a r. a > ST r (STRef r a)
read :: forall a r. STRef r a > ST r a
write :: forall a r. a > STRef r a > ST r a
modify :: forall r a. (a > a) > STRef r a > ST r a
new
is used to create a new mutable reference cell of type STRef r a
, which can be read using the read
action, and modified using the write
and modify
actions. The type a
is the type of the value stored in the cell, and the type r
is used to indicate a memory region (or heap) in the type system.
Here is an example. Suppose we want to simulate the movement of a particle falling under gravity by iterating a simple update function over a large number of small time steps.
We can do this by creating a mutable reference cell to hold the position and velocity of the particle, and then using a for
loop to update the value stored in that cell:
import Prelude
import Control.Monad.ST.Ref (modify, new, read)
import Control.Monad.ST (ST, for, run)
simulate :: forall r. Number > Number > Int > ST r Number
simulate x0 v0 time = do
ref < new { x: x0, v: v0 }
for 0 (time * 1000) \_ >
modify
( \o >
{ v: o.v  9.81 * 0.001
, x: o.x + o.v * 0.001
}
)
ref
final < read ref
pure final.x
At the end of the computation, we read the final value of the reference cell, and return the position of the particle.
Note that even though this function uses mutable state, it is still a pure function, so long as the reference cell ref
is not allowed to be used by other parts of the program. We will see that this is exactly what the ST
effect disallows.
To run a computation with the ST
effect, we have to use the run
function:
run :: forall a. (forall r. ST r a) > a
The thing to notice here is that the region type r
is quantified inside the parentheses on the left of the function arrow. That means that whatever action we pass to run
has to work with any region r
whatsoever.
However, once a reference cell has been created by new
, its region type is already fixed, so it would be a type error to try to use the reference cell outside the code delimited by run
. This is what allows run
to safely remove the ST
effect, and turn simulate
into a pure function!
simulate' :: Number > Number > Int > Number
simulate' x0 v0 time = run (simulate x0 v0 time)
You can even try running this function in PSCi:
> import Main
> simulate' 100.0 0.0 0
100.00
> simulate' 100.0 0.0 1
95.10
> simulate' 100.0 0.0 2
80.39
> simulate' 100.0 0.0 3
55.87
> simulate' 100.0 0.0 4
21.54
In fact, if we inline the definition of simulate
at the call to run
, as follows:
simulate :: Number > Number > Int > Number
simulate x0 v0 time =
run do
ref < new { x: x0, v: v0 }
for 0 (time * 1000) \_ >
modify
( \o >
{ v: o.v  9.81 * 0.001
, x: o.x + o.v * 0.001
}
)
ref
final < read ref
pure final.x
then the compiler will notice that the reference cell is not allowed to escape its scope, and can safely turn ref
into a var
. Here is the generated JavaScript for simulate
inlined with run
:
var simulate = function (x0) {
return function (v0) {
return function (time) {
return (function __do() {
var ref = { value: { x: x0, v: v0 } };
Control_Monad_ST_Internal["for"](0)(time * 1000  0)(function (v) {
return Control_Monad_ST_Internal.modify(function (o) {
return {
v: o.v  9.81 * 1.0e3,
x: o.x + o.v * 1.0e3
};
})(ref);
})();
return ref.value.x;
})();
};
};
};
Note that this resulting JavaScript is not as optimal as it could be. See this issue for more details. The above snippet should be updated once that issue is resolved.
For comparison, this is the generated JavaScript of the noninlined form:
var simulate = function (x0) {
return function (v0) {
return function (time) {
return function __do() {
var ref = Control_Monad_ST_Internal["new"]({ x: x0, v: v0 })();
Control_Monad_ST_Internal["for"](0)(time * 1000  0)(function (v) {
return Control_Monad_ST_Internal.modify(function (o) {
return {
v: o.v  9.81 * 1.0e3,
x: o.x + o.v * 1.0e3
};
})(ref);
})();
var $$final = Control_Monad_ST_Internal.read(ref)();
return $$final.x;
};
};
};
};
The ST
effect is a good way to generate short JavaScript when working with locallyscoped mutable state, especially when used together with actions like for
, foreach
, and while
which generate efficient loops.
Exercises
 (Medium) Rewrite the
safeDivide
function to throw an exception usingthrowException
if the denominator is zero.  (Skip) There is no exercise for
ST
yet. Feel free to propose one. See this issue for more details.
DOM Effects
In the final sections of this chapter, we will apply what we have learned about effects in the Effect
monad to the problem of working with the DOM.
There are a number of PureScript packages for working directly with the DOM, or with opensource DOM libraries. For example:
webdom
provides type definitions and low level interface implementations for the W3C DOM spec.webhtml
provides type definitions and low level interface implementations for the W3C HTML5 spec.jquery
is a set of bindings to the jQuery library.
There are also PureScript libraries which build abstractions on top of these libraries, such as
thermite
, which builds onreact
reactbasichooks
, which builds onreactbasic
halogen
which provides a typesafe set of abstractions on top of a custom virtual DOM library.
In this chapter, we will use the reactbasichooks
library to add a user interface to our address book application, but the interested reader is encouraged to explore alternative approaches.
An Address Book User Interface
Using the reactbasichooks
library, we will define our application as a React component. React components describe HTML elements in code as pure data structures, which are then efficiently rendered to the DOM. In addition, components can respond to events like button clicks. The reactbasichooks
library uses the Effect
monad to describe how to handle these events.
A full tutorial for the React library is well beyond the scope of this chapter, but the reader is encouraged to consult its documentation where needed. For our purposes, React will provide a practical example of the Effect
monad.
We are going to build a form which will allow a user to add a new entry into our address book. The form will contain text boxes for the various fields (first name, last name, city, state, etc.), and an area in which validation errors will be displayed. As the user types text into the text boxes, the validation errors will be updated.
To keep things simple, the form will have a fixed shape: the different phone number types (home, cell, work, other) will be expanded into separate text boxes.
You can launch the web app from the exercises/chapter8
directory with the following commands:
$ npm install
$ npx spago build
$ npx parcel src/index.html open
If development tools such as spago
and parcel
are installed globally, then the npx
prefix may be omitted. You have likely already installed spago
globally with npm i g spago
, and the same can be done for parcel
.
parcel
should launch a browser window with our "Address Book" app. If you keep the parcel
terminal open, and rebuild with spago
in another terminal, the page should automatically refresh with your latest edits. You can also configure automatic rebuilds (and therefore automatic page refresh) on filesave if you're using an editor that supports purs ide
or are running pscid
.
In this Address Book app, you should be able to enter some values into the form fields and see the validation errors printed onto the page.
Let's explore how it works.
The src/index.html
file is minimal:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF8">
<title>Address Book</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous">
</head>
<body>
<div id="container"></div>
<script src="./index.js"></script>
</body>
</html>
The <script
line includes the JavaScript entry point, index.js
, which contains this single line:
require("../output/Main/index.js").main();
It calls our generated JavaScript equivalent of the main
function of module Main
(src/main.purs
). Recall that spago build
puts all generated JavaScript in the output
directory.
The main
function uses the DOM and HTML APIs to render our address book component within the container
element we defined in index.html
:
main :: Effect Unit
main = do
log "Rendering address book component"
 Get window object
w < window
 Get window's HTML document
doc < document w
 Get "container" element in HTML
ctr < getElementById "container" $ toNonElementParentNode doc
case ctr of
Nothing > throw "Container element not found."
Just c > do
 Create AddressBook react component
addressBookApp < mkAddressBookApp
let
 Create JSX node from react component. Passin empty props
app = element addressBookApp {}
 Render AddressBook JSX node in DOM "container" element
D.render app c
Note that these three lines:
w < window
doc < document w
ctr < getElementById "container" $ toNonElementParentNode doc
Can be consolidated to:
doc < document =<< window
ctr < getElementById "container" $ toNonElementParentNode doc
Or consolidated even further to:
ctr < getElementById "container" =<< (map toNonElementParentNode $ document =<< window)
It is a matter of personal preference whether the intermediate w
and doc
variables aid in readability.
Let's dig into our AddressBook component
. We'll start with a simplified component, and then build up to the actual code in Main.purs
.
Take a look at this minimal component. Feel free to substitute the full component with this one to see it run:
mkAddressBookApp :: Effect (ReactComponent {})
mkAddressBookApp =
component
"AddressBookApp"
(\props > pure $ D.text "Hi! I'm an address book")
component
has this intimidating signature:
component :: forall hooks props.
Lacks "children" props =>
Lacks "key" props =>
Lacks "ref" props =>
String >
(Record props > Render Unit hooks JSX) >
Effect (ReactComponent (Record props))
The important points to note are the arguments after all the type class constraints. It takes a String
(an arbritrary component name), a function that describes how to convert props
into rendered JSX
, and returns our ReactComponent
wrapped in an Effect
.
The propstoJSX function is simply:
\props > pure $ D.text "Hi! I'm an address book"
props
are ignored, D.text
returns JSX
, and pure
lifts to rendered JSX. Now component
has everything it needs to produce the ReactComponent
.
Next we'll examine some of the additional complexities of the full Address Book component.
These are the first few lines of our full component:
mkAddressBookApp :: Effect (ReactComponent {})
mkAddressBookApp = do
component "AddressBookApp" \props > R.do
Tuple person setPerson < useState examplePerson
We track person
as a piece of state with the useState
hook.
Tuple person setPerson < useState examplePerson
Note that you are free to breakup component state into multiple pieces of state with multiple calls to useState
. For example, we could rewrite this app to use a separate piece of state for each record field of Person
, but that happens to result in a slightly less convenient architecture in this case.
In other examples, you may encounter the /\
infix operator for Tuple
. This is equivalent to the above line:
firstName /\ setFirstName < useState p.firstName
useState
takes a default initial value and returns the current value and a way to update the value. We can check the type of useState
to gain more insight the types of person
and setPerson
:
useState ::
forall state.
state >
Hook (UseState state) (Tuple state ((state > state) > Effect Unit))
We can strip the Hook (UseState state)
wrapper off of the return value because useState
is called within an R.do
block. We'll elaborate on R.do
later.
So now we can observe the following signatures:
person :: state
setPerson :: (state > state) > Effect Unit
The specific type of state
is determined by our initial default value. Person
Record
in this case because that is the type of examplePerson
.
person
is how we access the current state at each rerender.
setPerson
is how we update the state. We simply provide a function that describes how to transform the current state to the new state. The record update syntax is perfect for this when the type of state
happens to be a Record
, for example:
setPerson (\currentPerson > currentPerson {firstName = "NewName"})
or as shorthand:
setPerson _ {firstName = "NewName"}
NonRecord
states can also follow this update pattern. See this guide for more details on best practices.
Recall that useState
is used within an R.do
block. R.do
is a special react hooks variant of do
. The R.
prefix "qualifies" this as coming from React.Basic.Hooks
, and means we use their hookscompatible version of bind
in the R.do
block. This is known as a "qualified do". It lets us ignore the Hook (UseState state)
wrapping and bind the inner Tuple
of values to variables.
Another possible state management strategy is with useReducer
, but that is outside the scope of this chapter.
Rendering JSX
occurs here:
pure
$ D.div
{ className: "container"
, children:
renderValidationErrors errors
<> [ D.div
{ className: "row"
, children:
[ D.form_
$ [ D.h3_ [ D.text "Basic Information" ]
, formField "First Name" "First Name" person.firstName \s >
setPerson _ { firstName = s }
, formField "Last Name" "Last Name" person.lastName \s >
setPerson _ { lastName = s }
, D.h3_ [ D.text "Address" ]
, formField "Street" "Street" person.homeAddress.street \s >
setPerson _ { homeAddress { street = s } }
, formField "City" "City" person.homeAddress.city \s >
setPerson _ { homeAddress { city = s } }
, formField "State" "State" person.homeAddress.state \s >
setPerson _ { homeAddress { state = s } }
, D.h3_ [ D.text "Contact Information" ]
]
<> renderPhoneNumbers
]
}
]
}
Here we produce JSX
which represents the intended state of the DOM. This JSX is typically created by applying functions corresponding to HTML tags (e.g. div
, form
, h3
, li
, ul
, label
, input
) which create single HTML elements. These HTML elements are actually React components themselves, converted to JSX. There are usually three variants of each of these functions:
div_
: Accepts an array of child elements. Uses default attributes.div
: Accepts aRecord
of attributes. An array of child elements may be passed to thechildren
field of this record.div'
: Same asdiv
, but returns theReactComponent
before conversion toJSX
.
To display validation errors (if any) at the top of our form, we create a renderValidationErrors
helper function that turns the Errors
structure into an array of JSX. This array is prepended to the rest of our form.
renderValidationErrors :: Errors > Array R.JSX
renderValidationErrors [] = []
renderValidationErrors xs =
let
rendererror :: String > R.JSX
rendererror err = D.li_ [ D.text err ]
in
[ D.div
{ className: "alert alertdanger row"
, children: [ D.ul_ (map rendererror xs) ]
}
]
Note that since we are simply manipulating regular data structures here, we can use functions like map
to build up more interesting elements:
children: [ D.ul_ (map rendererror xs)]
We use the className
property to define classes for CSS styling. We're using the Bootstrap stylesheet
for this project, which is imported in index.html
. For example, we want items in our form arranged as row
s, and validation errors to be emphasized with alertdanger
styling:
className: "alert alertdanger row"
A second helper function is formField
, which creates a text input for a single form field:
formField :: String > String > String > (String > Effect Unit) > R.JSX
formField name placeholder value setValue =
D.label
{ className: "formgroup row"
, children:
[ D.div
{ className: "colsm colformlabel"
, children: [ D.text name ]
}
, D.div
{ className: "colsm"
, children:
[ D.input
{ className: "formcontrol"
, placeholder
, value
, onChange:
let
handleValue :: Maybe String > Effect Unit
handleValue (Just v) = setValue v
handleValue Nothing = pure unit
in
handler targetValue handleValue
}
]
}
]
}
Putting the input
and display text
in a label
aids in accessibility for screen readers.
The onChange
attribute allows us to describe how to respond to user input. We use the handler
function, which has the following type:
handler :: forall a. EventFn SyntheticEvent a > (a > Effect Unit) > EventHandler
For the first argument to hander
we use we use targetValue
, which provides the value of the text within the HTML input
element. It matches the signature expected by handler
where the type variable a
in this case is Maybe String
:
targetValue :: EventFn SyntheticEvent (Maybe String)
In JavaScript, the input
element's onChange
event is actually accompanied by a String
value, but since strings in JavaScript can be null, Maybe
is used for safety.
The second argument to handler
, (a > Effect Unit)
, must therefore have this signature:
Maybe String > Effect Unit
It is a function that describes how to convert this Maybe String
value into our desired effect. We define a custom handleValue
function for this purpose and pass it to handler
as follows:
onChange:
let
handleValue :: Maybe String > Effect Unit
handleValue (Just v) = setValue v
handleValue Nothing = pure unit
in
handler targetValue handleValue
setValue
is the function we provided to each formField
call that takes a string and makes the appropriate recordupdate call to the setPerson
hook.
Note that handleValue
can be substituted as:
onChange: handler targetValue $ traverse_ setValue
Feel free to investigate the definition of traverse_
to see how both forms are indeed equivalent.
That covers the basics of our component implementation. However, you should read the source accompanying this chapter in order to get a full understanding of the way the component works.
Obviously, this user interface can be improved in a number of ways. The exercises will explore some ways in which we can make the application more usable.
Exercises
 (Easy) Modify the application to include a work phone number text box.
 (Medium) Instead of using a
ul
element to show the validation errors in a list, modify the code to create onediv
with thealert
style for each error.  (Difficult, Extended) One problem with this user interface is that the validation errors are not displayed next to the form fields they originated from. Modify the code to fix this problem.
Hint: the error type returned by the validator should be extended to indicate which field caused the error. You might want to use the following modified Errors
type:
data Field = FirstNameField
 LastNameField
 StreetField
 CityField
 StateField
 PhoneField PhoneType
data ValidationError = ValidationError String Field
type Errors = Array ValidationError
You will need to write a function which extracts the validation error for a particular Field
from the Errors
structure.
Conclusion
This chapter has covered a lot of ideas about handling sideeffects in PureScript:
 We met the
Monad
type class, and its connection to do notation.  We introduced the monad laws, and saw how they allow us to transform code written using do notation.
 We saw how monads can be used abstractly, to write code which works with different sideeffects.
 We saw how monads are examples of applicative functors, how both allow us to compute with sideeffects, and the differences between the two approaches.
 The concept of native effects was defined, and we met the
Effect
monad, which is used to handle native sideeffects.  We used the
Effect
monad to handle a variety of effects: random number generation, exceptions, console IO, mutable state, and DOM manipulation using React.
The Effect
monad is a fundamental tool in realworld PureScript code. It will be used in the rest of the book to handle sideeffects in a number of other usecases.