III. Monads and the (>>=) Combinator

We have seen two functions, putStrLn and (>>), belonging to the IO monad. Of these, only (>>) is required of all monads. Both putStrLn and its coworker getLine help give the IO monad its unique capabilities. This section introduces a second function required of all monads and also provides a rather concrete explanation of the parameter a in IO a.

Suppose we want to echo user input. We would want to use the getLine function which obtains a line of user input from the console. But we cannot write

putStrLn getLine

because the type signatures don't match; getLine produces an IO String and putStrLn requires a String. The type signatures don't match because of Haskell's way of dealing with the potential impurity of getLine. Haskell has getLine return a monadic object instead of a string.

Because getLine represents a monadic object it is opaque. That means the string it obtains cannot be seen. The potential impurity of getLine is squashed. The string that getLine obtains is still there but unavailable in any context that would violate referential transparency. Said bluntly, code that invokes getLine cannot tell the difference between different evaluations of getLine.

This situation is something like riding a bicycle and having a car turn left immediately in front of you. Your immediate concern is avoiding the car. If the car hits you, the police will be concerned with finding the driver. When you deal with a monadic object, it is similarly opaque. But there are ways its “door” can be opened and then you are dealing with something on the interior rather than the object as a whole.

I will call the string obtained by an execution of getLine an interior object. It is interior to the monadic object which getLine returns.

The monadic combinator (>>=) produces the context where an interior object can be viewed. Its type signature is

(>>=) :: Monad m =>
            m a -> (a -> m b) -> m b

where m is any monad and the types a and b are arbitrary.

An application of (>>=) would look like

mon >>= f

where mon is of type m a and f is of type a -> m b. It is worth emphasizing that a and b can, but need not, be different types.

It is f's job to make an object mon2 out of one of mon's interior objects. It is (>>=)'s job to decide what to do with those mon2s.

Returning to the IO monad consider

main = 
  getLine >>= putStrLn

Here is an explanation of how main is executed.

You will benefit by comparing this description with a similar one for the State monad when you get around to reading Section IX.

A major difference between (>>=) and (>>) is that (>>=) can make use of interior objects and (>>) cannot. That (>>) will ignore any argument given to it is expressed in this definition.

(>>) mon1 mon2 = mon1 >>= \_ -> mon2

This is the normal way to define (>>) and we will assume it from now on.

Because (>>) is defined in terms of (>>=), (>>=) is the more important combinator to talk about.