While learning Haskell I have faced a lot of tutorials trying to explain what are monads and why monads are important in Haskell. Each of them used analogies so it would be easier to catch the meaning.
At the end of the day, I have end up with 3 differents view of what a monad is:
View 1: Monad as a label
Sometimes I think that a monad as a label to a specific type.
For example, a function of type:
myfunction :: IO Int
myfunction is a function that whenever is performed it will yield an Int value.
The type of the result is not Int but IO Int. So, IO is a label of the Int value warning the user to know that the Int value is the result of a process where a IO action has been made.
Consequently, this Int value has been marked as value that came from a process with IO therefore this value is "dirty". Your process is not pure anymore.
View 2: Monad as a private space where nasty things can happen.
In a system where all the process are pure and strict sometimes you need to have side-effects. So, a monad is just a little space that is allow to you for doing nasty side-effects.
In this space your are allow to escape the pure world, go the impure, make your process and then come back with a value.
View 3: Monad as in category theory
This is the view that I don't fully understand.
A monad is just a functor to the same category or a sub-category.
For example, you have the Int values and as a subcategory IO Int, that are the Int values generated after a IO process.
Are these views correct? Which is more accurate?
Best Answer
Views #1 and #2 are incorrect in general.
* -> *
can work as a label, monads are much more than that.IO
monad) computations within a monad are not impure. They simply represent computations that we perceive as having side effects, but they're pure.Both these misunderstandings come from focusing on the
IO
monad, which is actually a bit special.I'll try to elaborate on #3 a bit, without getting into category theory if possible.
Standard computations
All computations in a functional programming language can be viewed as functions with a source type and a target type:
f :: a -> b
. If a function has more than one argument, we can convert it to an one-argument function by currying (see also Haskell wiki). And if we have just a valuex :: a
(a function with 0 arguments), we can convert it into a function that takes an argument of the unit type:(\_ -> x) :: () -> a
.We can build more complex programs form simpler ones by composing such functions using the
.
operator. For example, if we havef :: a -> b
andg :: b -> c
we getg . f :: a -> c
. Note that this works for our converted values too: If we havex :: a
and convert it into our representation, we getf . ((\_ -> x) :: () -> a) :: () -> b
.This representation has some very important properties, namely:
id :: a -> a
for each typea
. It is an identity element with respect to.
:f
is equal both tof . id
and toid . f
..
is associative.Monadic computations
Suppose we want to select and work with some special category of computations, whose result contains something more than just the single return value. We don't want to specify what "something more" means, we want to keep things as general as possible. The most general way to represent "something more" is representing it as a type function - a type
m
of kind* -> *
(i.e. it converts one type to another). So for each category of computations we want to work with, we'll have some type functionm :: * -> *
. (In Haskell,m
is[]
,IO
,Maybe
, etc.) And the category will contains all functions of typesa -> m b
.Now we would like to work with the functions in such a category in the same way as in the basic case. We want to be able to compose these functions, we want the composition to be associative, and we want to have an identity. We need:
<=<
) that composes functionsf :: a -> m b
andg :: b -> m c
into something asg <=< f :: a -> m c
. And, it must be associative.return
. We also want thatf <=< return
is the same asf
and the same asreturn <=< f
.Any
m :: * -> *
for which we have such functionsreturn
and<=<
is called a monad. It allows us to create complex computations from simpler ones, just as in the basic case, but now the types of return values are tranformed bym
.(Actually, I slightly abused the term category here. In the category-theory sense we can call our construction a category only after we know it obeys these laws.)
Monads in Haskell
In Haskell (and other functional languages) we mostly work with values, not with functions of types
() -> a
. So instead of defining<=<
for each monad, we define a function(>>=) :: m a -> (a -> m b) -> m b
. Such an alternative definition is equivalent, we can express>>=
using<=<
and vice versa (try as an exercise, or see the sources). The principle is less obvious now, but it remains the same: Our results are always of typesm a
and we compose functions of typesa -> m b
.For each monad we create, we must not forget to check that
return
and<=<
have the properties we required: associativity and left/right identity. Expressed usingreturn
and>>=
they are called the monad laws.An example - lists
If we choose
m
to be[]
, we get a category of functions of typesa -> [b]
. Such functions represent non-deterministic computations, whose results could be one or more values, but also no values. This gives rise to the so-called list monad. The composition off :: a -> [b]
andg :: b -> [c]
works as follows:g <=< f :: a -> [c]
means to compute all possible results of type[b]
, applyg
to each of them, and collect all the results in a single list. Expressed in Haskellor using
>>=
Note that in this example the return types were
[a]
so it was possible that they didn't contain any value of typea
. Indeed, there is no such requirement for a monad that the return type should have such values. Some monads always have (likeIO
orState
), but some don't, like[]
orMaybe
.The IO monad
As I mentioned, the
IO
monad is somewhat special. A value of typeIO a
means a value of typea
constructed by interacting with the program's environment. So (unlike all the other monads), we cannot describe a value of typeIO a
using some pure construction. HereIO
is simply a tag or a label that distinguishes computations that interact with the environment. This is (the only case) where the views #1 and #2 are correct.For the
IO
monad:f :: a -> IO b
andg :: b -> IO c
means: Computef
that interacts with the environment, and then computeg
that uses the value and computes the result interacting with the environment.return
just adds theIO
"tag" to the value (we simply "compute" the result by keeping the environment intact).Some notes:
m a
, there is no way how to "escape" from theIO
monad. The meaning is: Once a computation interacts with the environment, you cannot construct a computation from it that doesn't.IO
monad. This is whyIO
is often called a programmer's sin bin.getChar
must have a result type ofIO something
.