You could see a given monad m
as a set/family (or realm, domain, etc.) of actions (think of a C statement). The monad m
defines the kind of (side-)effects that its actions may have:
- with
[]
you can define actions which can fork their executions in different "independent parallel worlds";
- with
Either Foo
you can define actions which can fail with errors of type Foo
;
- with
IO
you can define actions which can have side-effects on the "outside world" (access files, network, launch processes, do a HTTP GET ...);
- you can have a monad whose effect is "randomness" (see package
MonadRandom
);
- you can define a monad whose actions can make a move in a game (say chess, Go…) and receive move from an opponent but are not able to write to your filesystem or anything else.
Summary
If m
is a monad, m a
is an action which produces a result/output of type a
.
The >>
and >>=
operators are used to create more complex actions out of simpler ones:
a >> b
is a macro-action which does action a
and then action b
;
a >> a
does action a
and then action a
again;
- with
>>=
the second action can depend on the output of the first one.
The exact meaning of what an action is and what doing an action and then another one is depends on the monad: each monad defines an imperative sublanguage with some features/effects.
Simple sequencing (>>
)
Let's say with have a given monad M
and some actions incrementCounter
, decrementCounter
, readCounter
:
instance M Monad where ...
incrementCounter :: M ()
decrementCounter :: M ()
readCounter :: M Integer
Now we would like to do something interesting with those actions. The first thing we would like to do with those actions is to sequence them. As in say C, we would like to be able to do:
// This is C:
counter++;
counter++;
We define an "sequencing operator" >>
. Using this operator we can write:
incrementCounter >> incrementCounter
What is the type of "incrementCounter >> incrementCounter"?
It is an action made of two smaller actions like in C you can write composed-statements from atomic statements :
{
counter++;
counter++;
}
if (condition) {
counter++;
counter++;
}
it can have the same kind of effects as its subactions;
it does not produce any output/result.
So we would like incrementCounter >> incrementCounter
to be of type M ()
: an (macro-)action with the same kind of possible effects but without any output.
More generally, given two actions:
action1 :: M a
action2 :: M b
we define a a >> b
as the macro-action which is obtained by doing (whatever that means in our domain of action) a
then b
and produces as output the result of the execution of the second action. The type of >>
is:
(>>) :: M a -> M b -> M b
or more generally:
(>>) :: (Monad m) => m a -> m b -> m b
We can define bigger sequence of actions from simpler ones:
action1 >> action2 >> action3 >> action4
Input and outputs (>>=
)
We would like to be able to increment by something else that 1 at a time:
incrementBy 5
We want to provide some input in our actions, in order to do this we define a function incrementBy
taking an Int
and producing an action:
incrementBy :: Int -> M ()
Now we can write things like:
incrementCounter >> readCounter >> incrementBy 5
But we have no way to feed the output of readCounter
into incrementBy
. In order to do this, a slightly more powerful version of our sequencing operator is needed. The >>=
operator can feed the output of a given action as input to the next action. We can write:
readCounter >>= incrementBy
It is an action which executes the readCounter
action, feeds its output in the incrementBy
function and then execute the resulting action.
The type of >>=
is:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
A (partial) example
Let's say I have a Prompt
monad which can only display informations (text) to the user and ask informations to the user:
module Prompt (Prompt(), echo, prompt) where
data Prompt a = ...
instance Monad Prompt where ...
echo :: String -> Prompt ()
prompt :: String -> Prompt String
Let's try to define a promptBoolean message
actions which asks for a question and produces a boolean value.
We use the prompt (message ++ "[y/n]")
action and feed its output to a function f
:
f "y"
should be an action which does nothing but produce True
as output;
f "n"
should be an action which does nothing but produce False
as output;
anything else should restart the action (do the action again);
promptBoolean
would look like this:
promptBoolean :: String -> M Boolean
promptBoolean message = prompt (message ++ "[y/n]") >>= f
where f result = if result == "y"
then ????
else if result=="n"
then ????
else echo "Input not recognised, try again." >> promptBoolean
Producing a value without effect (return
)
In order to fill the missing bits in our promptBoolean
function, we need a way to represent dummy actions without any side effect but which only outputs a given value:
-- "return 5" is an action which does nothing but outputs 5
return :: (Monad m) => a -> m a
and we can now write out promptBoolean
function:
promptBoolean :: String -> Prompt Boolean
promptBoolean message :: prompt (message ++ "[y/n]") >>= f
where f result = if result=="y"
then return True
else if result=="n"
then return False
else echo "Input not recognised, try again." >> promptBoolean message
By composing those two simple actions (promptBoolean
, echo
) we can define any kind of dialogue between the user and your program (the actions of the program are deterministic as our monad does not have a "randomness effect").
promptInt :: String -> M Int
promptInt = ... -- similar
-- Classic "guess a number game/dialogue"
guess :: Int -> m()
guess n = promptInt "Guess:" m -> f
where f m = if m == n
then echo "Found"
else (if m > n
then echo "Too big"
then echo "Too small") >> guess n
The operations of a monad
A Monad is a set of actions which can be composed with the return
and >>=
operators:
These two operators are the minimal operators needed to define a Monad
.
In Haskell, the >>
operator is needed as well but it can in fact be derived from >>=
:
(>>): Monad m => m a -> m b -> m b
a >> b = a >>= f
where f x = b
In Haskell, an extra fail
operator is need as well but this is really a hack (and it might be removed from Monad
in the future).
This is the Haskell definition of a Monad
:
class Monad m where
return :: m a
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b -- can be derived from (>>=)
fail :: String -> m a -- mostly a hack
Actions are first-class
One great thing about monads is that actions are first-class. You can take them in a variable, you can define function which take actions as input and produce some other actions as output. For example, we can define a while
operator:
-- while x y : does action y while action x output True
while :: (Monad m) => m Boolean -> m a -> m ()
while x y = x >>= f
where f True = y >> while x y
f False = return ()
Summary
A Monad
is a set of actions in some domain. The monad/domain define the kind of "effects" which are possible. The >>
and >>=
operators represent sequencing of actions and monadic expression may be used to represent any kind of "imperative (sub)program" in your (functional) Haskell program.
The great things are that:
you can design your own Monad
which supports the features and effects that you want
see Prompt
for an example of a "dialogue only subprogram",
see Rand
for an example of "sampling only subprogram";
you can write your own control structures (while
, throw
, catch
or more exotic ones) as functions taking actions and composing them in some way to produce a bigger macro-actions.
MonadRandom
A good way of understanding monads, is the MonadRandom
package. The Rand
monad is made of actions whose output can be random (the effect is randomness). An action in this monad is some kind of random variable (or more exactly a sampling process):
-- Sample an Int from some distribution
action :: Rand Int
Using Rand
to do some sampling/random algorithms is quite interesting because you have random variables as first class values:
-- Estimate mean by sampling nsamples times the random variable x
sampleMean :: Real a => Int -> m a -> m a
sampleMean n x = ...
In this setting, the sequence
function from Prelude
,
sequence :: Monad m => [m a] -> m [a]
becomes
sequence :: [Rand a] -> Rand [a]
It creates a random variable obtained by sampling independently from a list of random variables.
Reader
,Writer
,State
,[]
(list), continuations, various monads for parsing, monads for creating HTML/JS/CSS in web applications, and probably many more. Besides, I'd argue that it does help to understand what monads aren't - you won't see the full power of computers when you're convinced their sole use is doing arithmetic quicker ;) – user395760