Monads in Haskell are sometimes referred to as "programmable semicolons". It's not a phrase I find particularly helpful in general, but it does capture the way that expressions written with Haskell's do
notation have something of the flavour of imperative programs. And in particular that the way the "statements" in a do
block get combined is dependent on the particular monad being used. Hence "programmable semicolons" - the way successive "statements" (which in many imperative languages are separated by semicolons) combine together can be changed ("programmed") by using a different monad.
And since do
notation is really just syntactic sugar for building up an expression from others using the >>=
operator, it's the implementation of >>=
for each monad that determines what its "special behaviour" is.
For example, the Monad
instance for Maybe
allows one, as a rough description, to work with Maybe
values as if they are actually values of the underlying type, while ensuring that if a non-value (that is, Nothing
) occurs at any point, the computation short-circuits and Nothing
will be the overall result.
For the list monad, every line actually gets "executed" multiple times (or none) - once for each element in the list.
And for values of the State s
monad, these are essentially "state manipulation functions" of type s -> (a, s)
- they take an initial state, and from that compute a new state as well as an output value of some type a
. What the >>=
implementation - the "semicolon" - does here* is simply ensure that, when one function f :: s -> (a, s)
is followed by another g :: s -> (b, s)
, that the resulting function applies f
to the initial state and then applies g
to the state computed from f
. It's basically just function composition, slightly modified so as to also allow us to access an "output value" whose type is not necessarily related to that of the state. And this allows one to list various state manipulation functions one after another in a do
block and know that the state at each stage is exactly that computed by the previous lines put together. This in turn allows a very natural programming style where you give successive "commands" for manipulating the state, yet without actually doing destructive updates, or otherwise departing from the world of pure functions and immutable data.
*strictly speaking, this isn't >>=
but >>
, an operation which is derived from >>=
but ignores the output value. You may have noticed that in the example I gave the a
value output by f
is totally ignored - but >>=
allows that value to be inspected and to determine which computation to do next. In do
notation, this means writing a <- f
and then using a
later. This is actually the key thing which distinguishes Monads from their less powerful, but still vital, cousins (notably Applicative functors).