The Effect
monad, for handling native side effects.
spago install effect
Values in PureScript do not have side-effects by default. This package provides the standard type PureScript uses to handle "native" effects, i.e. effects which are provided by the runtime system, and which cannot be emulated by pure functions. 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
All of these things may be represented in PureScript by the Effect
type.
A value of the type Effect a
represents a computation which may perform
some native effects, and which produces a value of the type a
once it
finishes. For example, the module Effect.Random
from purescript-random
exports a value random
, whose type is Effect Number
. When run, this
effect produces a random number between 0 and 1. To give another example,
the module Effect.Console
exports a function log
, whose type is
String -> Effect Unit
. This function takes a string and produces an
effect which, when run, prints the provided string to the console.
module RandomExample where
import Prelude
import Effect (Effect)
import Effect.Random (random)
import Effect.Console (log)
printRandom :: Effect Unit
printRandom = do
n <- random
log (show n)
In this example, printRandom
is an effect which generates a random
number between 0 and 1 and prints it to the console. You can test it out
in the repl:
> import RandomExample
> printRandom
0.71831842260513870
unit
The unit
is there because all effects must produce a value. When there
is nothing useful to return, we usually use the type Unit
, which has
just one value: unit
.
Note that using Effect
does not compromise the purity of your program.
Functions which make use of Effect
are still pure, in the sense that
all functions will return the same outputs given the same inputs.
This is enabled by having the Effect
type denote values which can be
run to produce native effects; an Effect a
is a computation which has
not yet necessarily been performed. In fact, there is no way of "running"
an Effect
manually; we cannot provide a (safe) function of the type
forall a. Effect a -> a
, because such a function would violate purity;
it could produce different outputs given the same input. (Note that there
is actually such a function in the module Effect.Unsafe
, but it is best
avoided outside of exceptional circumstances.)
Instead, the recommended way of "running" an effect is to include it as
part of your program's main
value.
A consequence of being able to represent native effects purely using
Effect
is that you can construct and pass Effect
values around your
programs freely. For example, we can write functions which can decide
whether to run a given effect just once, many times, or not at all:
-- | Runs an effect three times, provided that the given condition is
-- | true.
thriceIf :: Boolean -> Effect Unit -> Effect Unit
thriceIf cond effect =
if cond
then do
effect
effect
effect
else
pure unit
A computation of type Effect a
is implemented in JavaScript as a
zero-argument function whose body is expected to perform its side-effects
before finally returning its result. For example, suppose we wanted a
computation which would increment a global counter and return the new
result each time it was run. We can implement this as follows:
-- Counter.purs
foreign import incrCounter :: Effect Int
and in the corresponding JavaScript module:
// Counter.js
exports.incrCounter = function () {
if (!window.globalCounter) {
window.globalCounter = 0;
}
return ++window.globalCounter;
};
For more information about the FFI (Foreign Function Interface), see the documentation repository. This package also provides a module Effect.Uncurried to simplify the process of making uncurried effectful JavaScript functions available to PureScript via the FFI.
The PureScript compiler has special support for the Effect
monad.
Ordinarily, a chain of monadic binds might result in poor performance when
executed. However, the compiler can generate code for the Effect
monad
without explicit calls to the monadic bind function >>=
.
Take the random number generation example from above. When compiled, the compiler produces the following JavaScript:
var printRandom = function __do() {
var $0 = Effect_Random.random();
return Effect_Console.log(Data_Show.show(Data_Show.showNumber)($0))();
};
whereas a more naive compiler might, for instance, produce:
var printRandom = Control_Bind.bind(Effect.bindEffect)(Effect_Random.random)(
function ($0) {
return Effect_Console.log(Data_Show.show(Data_Show.showNumber)($0));
}
);
While this is a small improvement, the benefit is greater when using
multiple nested calls to >>=
.
Module documentation is published on Pursuit.