30

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?

3
  • 5
    #2 is not what a monad is in general. In fact, it's pretty much restricted to IO, and not a useful view (cf. What a Monad is not). Also, "strict" is generally taken to name a property Haskell does not posses (namely strict evaluation). By the way, Monads don't change that either (again, see What a Monad is not).
    – user7043
    Commented Aug 18, 2012 at 14:10
  • 3
    Techically, only third one is correct. Monad is endofunctor, for wich to special operation are defined - promotion and binding. Monads are numerous - a list monad is perfect example to get intuition behind monads. readS facilities are even better. Surprising enough, monads are usable as tools to thread state in pure functional language implicitly. This is not a defining property of monads: it is coincidence, that state threading may be implemented in their terms. Same applies to IO.
    – permeakra
    Commented Aug 18, 2012 at 18:46
  • Common Lisp has its own compiler as part of the language. Haskell has Monads.
    – Will Ness
    Commented May 30, 2019 at 12:53

3 Answers 3

35

Views #1 and #2 are incorrect in general.

  1. Any data-type of kind * -> * can work as a label, monads are much more than that.
  2. (With the exception of the 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 value x :: 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 have f :: a -> b and g :: b -> c we get g . f :: a -> c. Note that this works for our converted values too: If we have x :: a and convert it into our representation, we get f . ((\_ -> x) :: () -> a) :: () -> b.

This representation has some very important properties, namely:

  • We have a very special function - the identity function id :: a -> a for each type a. It is an identity element with respect to .: f is equal both to f . id and to id . f.
  • The function composition operator . 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 function m :: * -> *. (In Haskell, m is [], IO, Maybe, etc.) And the category will contains all functions of types a -> 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:

  • To have an operator (let's call it <=<) that composes functions f :: a -> m b and g :: b -> m c into something as g <=< f :: a -> m c. And, it must be associative.
  • To have some identity function for each type, let's call it return. We also want that f <=< return is the same as f and the same as return <=< f.

Any m :: * -> * for which we have such functions return 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 by m.

(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 types m a and we compose functions of types a -> 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 using return and >>= they are called the monad laws.

An example - lists

If we choose m to be [], we get a category of functions of types a -> [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 of f :: a -> [b] and g :: b -> [c] works as follows: g <=< f :: a -> [c] means to compute all possible results of type [b], apply g to each of them, and collect all the results in a single list. Expressed in Haskell

return :: a -> [a]
return x = [x]
(<=<) :: (b -> [c]) -> (a -> [b]) -> (a -> [c])
g (<=<) f  = concat . map g . f

or using >>=

(>>=) :: [a] -> (a -> [b]) -> [b]
x >>= f  = concat (map f x)

Note that in this example the return types were [a] so it was possible that they didn't contain any value of type a. Indeed, there is no such requirement for a monad that the return type should have such values. Some monads always have (like IO or State), but some don't, like [] or Maybe.

The IO monad

As I mentioned, the IO monad is somewhat special. A value of type IO a means a value of type a constructed by interacting with the program's environment. So (unlike all the other monads), we cannot describe a value of type IO a using some pure construction. Here IO 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:

  • Composition of f :: a -> IO b and g :: b -> IO c means: Compute f that interacts with the environment, and then compute g that uses the value and computes the result interacting with the environment.
  • return just adds the IO "tag" to the value (we simply "compute" the result by keeping the environment intact).
  • The monad laws (associativity, identity) are guaranteed by the compiler.

Some notes:

  1. Since monadic computations always have the result type of m a, there is no way how to "escape" from the IO monad. The meaning is: Once a computation interacts with the environment, you cannot construct a computation from it that doesn't.
  2. When a functional programmer doesn't know how to make something in a pure way, (s)he can (as the last resort) program the task by some stateful computation within the IO monad. This is why IO is often called a programmer's sin bin.
  3. Notice that in an impure world (in the sense of functional programming) reading a value can change the environment too (like consume user's input). That's why functions like getChar must have a result type of IO something.
3
  • 3
    Great answer. I'd clarify that IO does not have special semantics from a language standpoint. It is not special, it behaves like any other code. Only the runtime library implementation is special. Also, there is a special-purpose way to escape (unsafePerformIO). I think this is important because people often think of IO as a special language element or a declarative tag. It is not.
    – usr
    Commented Aug 19, 2012 at 19:24
  • 2
    @usr Good point. I'd add that unsafePerformIO is really unsafe and should be only used by experts. It allows you to break everything, for example, you can create a function coerce :: a -> b that converts any two types (and crash your program in most cases). See this example - you can convert even a function into Int etc.
    – Petr
    Commented Sep 10, 2012 at 19:46
  • Another "special magic" monad would be ST, which allows you to declare references to memory that you can read from and write to as you see fit (although only within the monad), and then you can extract a result by calling runST :: (forall s. GHC.ST.ST s a) -> a
    – sara
    Commented Jun 18, 2016 at 20:27
5

View 1: Monad as a label

"Consequently, this Int value has been marked as value that came from a process with IO therefore this value is "dirty"."

"IO Int" is not in general a Int value (although it may be in some cases such as "return 3"). It is a procedure which outputs some Int value. Different executions of this "procedure" may yield different Int values.

A monad m, is an embedded (imperative) "programming language": within this language it is possible to define some "procedures". A monadic value (of type m a), is a procedure in this "programming language" which outputs a value of type a.

For example:

foo :: IO Int

is some procedure which outputs a value of type Int.

Then:

bar :: IO (Int, Int)
bar = do
  a <- foo
  b <- foo
  return (a,b)

is some procedure which outputs two (possibly different) Ints.

Every such "language" supports some operations:

  • two procedures (ma and mb) may be "concatenated": you can creates a a larger procedure (ma >> mb) made of the first one then the second one;

  • what's more the output (a) of the first one may affect the second one (ma >>= \a -> ...);

  • a procedure (return x) may yield some constant value (x).

The different embedded programming languages differ on the kind things they support such as:

  • yielding random values;
  • "forking" (the [] monad);
  • exceptions (throw/catch) (The Either e monad);
  • explicit continuation/callcc support;
  • sending/receiving messages to other "agents";
  • create, set and read variables (local to this programming language) (the ST monad).
1

Don't confuse a monadic type with the monad class.

A monadic type (i.e. a type being an instance of the monad class) would solve a particular problem (in principle, each monadic type solves a different one): State, Random, Maybe, IO. All of them are types with context (what you call "label", but that is not what makes them to be a monad).

For all of them, there is the need of "chaining operations with choice" (one operation depends on the result of the previous). Here comes into play the monad class: have your type (solving a given problem) be an instance of the monad class and the chaining problem is solved.

See What does the monad class solve?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.