Home  Up

A (hopefully) painless introduction to monads

version 2
by Dan Bensen

Preface
What's a monad?
Why monads?
Computational patterns
bind (>>=)

Maybe
Why IO?
IO and main
Monad functions
then (>>)

Monad syntax
return
do syntax
Globals and State
Further reading

Preface

This introduction is intended for experienced programmers beginning to learn functional programming. All the code samples are in Haskell. Please note that this is not a full tutorial1. It's only an introduction to get you started.

What's a monad?

Monads are a class of data types used in pure functional programming. Monads are abstract types, which means that each monad represents a whole class of types. To make a specific type, you combine the monad with another type that describes the data inside the monad, which acts as a sort of container.

For example, IO is the monad associated with input/output, so  IO String  is the type of value returned by user input functions, and  IO Int  is the type returned by random-number generators. Once you've defined a monad, you can use it for data of any applicable type.

The data types that can work with a monad are determined by an operator that's defined for all monads. The operator is called bind, and is written as >>=bind is the most important feature of monads. It's overloaded with a unique definition for each monad that gives the monad its own characteristic behavior.

Why monads?

Monads serve two purposes:
  • encapsulation of common computational patterns
  • isolation of code that interacts with the outside world

Most monads are used to express frequently used computational patterns. This eliminates repetition in a program's source code by allowing computationally similar operations to be consolidated in the definition of bind for the appropriate monad.

The IO monad is used to handle all interactions with the program's external environment (I/O). This allows other functions to be combined with each other more easily.

Computational patterns

Monads reduce the amount of source code in a program by wrapping computational patterns in bind. There are several standard monads, each with its own particular behavior pattern:
MonadCharacteristic behavior

IO sequential execution of operations in time
Maybeoperations that may fail to return a value
List operations that can return any number of values
Stateoperations that need to share data

There are more standard monads, and you can create your own. Note that List is the type of plain old lists.

Computationally similar operations that act on different data types can then be consolidated by putting the data in an appropriate monad and using bind to combine the monad value with a function that operates on the data inside it.

bind (>>=)

Expressions using bind are written as  x >>= fx is an expression that returns a monad value of type  m a,  where the variable m represents a monad, and  a represents the type of value the monad contains. f is a function that takes a single argument2 of type  a  and returns a monad value of type  m b  for some type bbind also returns a value of type  m b

Maybe

Maybe is a good example of consolidating source code. A type  Maybe a  has two kinds of value: A single value called Nothing to represent a failed operation, and a class of values written as  Just x  that indicate successful computation of a value x of type a.

If you need to pass a value through a chain of operations that don't always succeed, nonmonadic code to call them might look like something this:

case test1 x in
    Nothing -> Nothing
    Just y  -> case test2 y in
                   Nothing -> Nothing
                   Just z  -> case test3 z in
                                  ...

Obviously there's a pattern here: The chain terminates and returns Nothing whenever Nothing is encountered, i.e. when one of the tests fails. It continues on success, passing the result of the last operation to the next one.

The definition of bind in Maybe encapsulates this pattern:

x >>= f = case x in Nothing -> Nothing
                    Just y  -> f y
This is the generic computational pattern of returning on failure. The monadic version of the code snippet above is then much more compact:
test1 x >>= test2 >>= test3 >>= ...
This is the main purpose of most monads. The source code has been shrunk down to a quarter of its original size. In large programs with multiple levels of monadic operations, you can consolidate the code even more.

Why IO?

The idea behind pure functional programming is to make functions in a computer program act just like mathematical functions: Given any specific set of arguments, a function should always return the same value, and it should never do anything else. It shouldn't read from or print to the user interface, and it shouldn't use or change any global variables.

This property is a big help:

  • Unit testing is much easier, because the behavior of each function is independent of the rest of the program.
  • The programmer doesn't have to worry about what order functions are called in, only what arguments they recieve. This applies even with multiple calls to the same function.
  • Since it doesn't matter what order functions are called in, as long as they have the right arguments, the compiler can produce better optimized code3, especially if multiple processors are available.

The only problem with pure functions is that they can't express real-world actions:

  • Input functions and random-number generators don't always return the same value for a given set of arguments.
  • Output functions have an effect on the outside world. This is independent of the function's return value, but dependent on the order in which function calls are made.
  • Functions that assign new values to global variables can affect the behavior of functions that use those variables.

IO and main

The IO monad is used to alleviate this problem. Placing a value in IO labels the value as "impure".4 The compiler keeps the processing of all impure operations separate from the rest of the program by requiring the programmer to handle them in functions that operate on data in the IO monad. This preserves the mathematical purity of other functions, allowing them to be optimized better and combined more freely.

Since IO functions interact with the outside world, which can't be passed to a function as an argument, pure functions aren't allowed to call them. So IO functions can only be called by other IO functions. This means the entry point of every pure functional program has to be an IO function. In Haskell, this function is main, just like in C. main initiates the chain of IO function calls, and pure functions can be called from anywhere in this chain.

Monad functions

A monad function is simply a function that returns a monad value. It can also take monad arguments. Inside the monad function, bind allows you to pull values out of their monads and safely pass the values to pure functions. You can think of the monad function as removing the contents of one or more monads, operating on the contents functionally, and putting the results back into a monad to be returned as the value of the function.

then (>>)

There's another monad operator, sometimes called then, which is written >>. then is defined in terms of bind:
x >> f = x >>= (\_ -> f)
The stuff in parentheses is a nameless function5 (indicated by the backslash) that takes an unnamed argument (the underline), as required by bind. But then ignores x and just calls f. then evaluates x only for its side effects. In this case, f should not take an argument2.

Basic syntax of monad functions

Here's a simple "Hello User" program:

Module Main where
getName = putStr "Please enter your name: " >> getLine
sayHello name = putStrLn ("Welcome, " ++ name)
main = getName >>= sayHello

All of these functions are IO functions. The only exception is ++, which concatenates bare strings. putStr, putStrLn, and getLine are built-in functions.

The following is the sequence of events. It differs from functional code in that the order of events is strictly preserved.

  1. main calls bind, which calls getName, which calls then, which calls putStr.
  2. putStr prints its text argument to the user's screen and returns a null value.
  3. then ignores the return value of putStr and calls getLine, which waits for the user to enter a line of text, wraps it in an IO monad, and returns the monad value to then, which returns it to getName, which returns it to bind.
  4. bind pulls the string out of the monad returned by getName and passes a greeting containing the string to sayHello, which calls putStrLn, which prints the greeting and returns a null value.

return

If you want to wrap the login greeting in a function that returns the user's name, you need to use a monad function called returnreturn doesn't do very much. It just puts a value back into a monad:
greetUser = getName >>= (\name -> sayHello name >> return name)

Note that return isn't an operator that returns from the current function, as it is in most languages. It's just a function that "returns" its argument to the current monad.

do notation

The basic bind syntax isn't too bad for a small function like greetUser, but for larger functions, it's not always easy to read. For real-life code, it's often more convenient to use do notation. The monad function begins with the keyword do, and contains actions in one of two formats:
  • separated by semicolons and surrounded by curly braces, as in C or Perl.
  • indented and separated by linebreaks, as in Python.
A left arrow (<-) binds the identifier on the left to the contents of the monad on the right.

greetUser = do { name <- getName; sayHello name; return name }
or
greetUser = do
  name <- getName
  sayHello name
  return name

Global variables and State

The State monad encapsulates shared data that might otherwise be contained in global variables. Unlike IO, values in State are passed between functions as arguments, keeping State within the functional model. State makes this process easier by putting many values into a single container.

Further reading

To continue learning about monads in Haskell, see these sources:

Please let me know if anything in this introduction was confusing or didn't answer a question you have. Likewise if you're an experienced Haskell hacker and you found any mistakes.

footnotes

1 Because there are plenty enough of those already.
2 Actually, f can take a larger number of arguments, call it n, but in that case, x >>= f will return a monad function of  n - 1 arguments, with the contents of x bound to the first argument of fx >> f simply returns f.
3 The theory of lambda calculus makes it much easier for the programmers who write compilers to build in optimization of pure functions. This is a very powerful tool, but is not possible for functions with side effects (I/O) or mutable data (variables).
4 This is the source of the space-suit and nuclear-waste metaphors.
5 A nameless function is usually called an "anonymous" function, a "lambda" function, or simply a "lambda". It may be too trivial to bother giving it a name, it may be used only once, and it may defined in terms of data that's determined at runtime, in which case it's called a "closure".

© 2006–2007 Dan Bensen   Home   About me  Site map