Haskell/do notation

Using  blocks as an alternative monad syntax was first introduced way back in the Simple input and output chapter. There, we used  to sequence input/output operations, but we hadn't introduced monads yet. Now, we can see that  is yet another monad.

Since the following examples all involve, we will refer to the computations/monadic values as actions (as we did in the earlier parts of the book). Of course,  works with any monad; there is nothing specific about   in how it works.

Translating the then operator
The  (then) operator works almost identically in   notation and in unsugared code. For example, suppose we have a chain of actions like the following one:

We can rewrite that in  notation as follows:

(using the optional braces and semicolons explicitly, for clarity). This sequence of instructions nearly matches that in any imperative language. In Haskell, we can chain any actions as long as all of them are in the same monad. In the context of the  monad, the actions include writing to a file, opening a network connection, or asking the user for an input.

Here's the step-by-step translation of  notation to unsugared Haskell code:

becomes

and so on, until the  block is empty.

Translating the bind operator
The bind operator  is a bit more difficult to translate to and from the   notation. passes a value, namely the result of an action or function, downstream in the binding sequence. notation assigns a variable name to the passed value using the.

The curly braces and the semicolons are optional if every line of code is indented to line up equally (NB: beware the mixing of tabs and spaces in that case; with the explicit curly braces and semicolons indentation plays no part and there's no danger).

and  are the results of   and. If, for instance,  is an   then   will be bound to an   value. The two bound values in this example are passed as arguments to, which creates a third action. The  block is broadly equivalent to the following vanilla Haskell snippet:

The second argument of the first (leftmost) bind operator is a function (lambda expression) specifying what to do with the result of the action passed as the bind's first argument. Thus, chains of lambdas pass the results downstream. The parentheses could be omitted, because a lambda expression extends as far as possible. is still in scope at the point we call the final action maker. We can rewrite the chain of lambdas more legibly by using separate lines and indentation:

That shows the scope of each lambda function clearly. To group things more like the  notation, we could show it like this:

These presentation differences are only a matter of assisting readability.

The fail method
Above, we said the snippet with lambdas was "broadly equivalent" to the  block. The translation is not exact because the  notation adds special handling of pattern match failures. When placed at the left of either  or ,   and   are patterns being matched. Therefore, if  returned a   we could write a   block like this...

...and  be an. In such a case, what happens if  returns  ? Ordinarily, the program would crash with an non-exhaustive patterns error, just like the one we get when calling  on an empty list. With  notation, however, failures are handled with the   method for the relevant monad. The  block above translates to:

What  actually does depends on the monad instance. Though it will often rethrow the pattern matching error, monads that incorporate some sort of error handling may deal with the failure in their own specific ways. For instance,  has ; analogously, for the list monad.

The  method is an artifact of   notation. Rather than calling  directly, you should rely on automatic handling of pattern match failures whenever you are sure that   will do something sensible for the monad you are using.

Example: user-interactive program
Consider this simple program that asks the user for their first and last names:

A possible translation into vanilla monadic code:

In cases like this, where we just want to chain several actions, the imperative style of  notation feels natural and convenient. In comparison, monadic code with explicit binds and lambdas is something of an acquired taste.

Notice that the first example above includes a  statement in the   block. The de-sugared version is simply a regular  expression where the   part is whatever follows from the   syntax.

Returning values
The last statement in  notation is the overall result of the   block. In the previous example, the result was of the type, i.e. an empty value in the   monad.

Suppose that we want to rewrite the example but return an  with the acquired name. All we need to do is add a :

This example will "return" the full name as a string inside the  monad, which can then be utilized downstream elsewhere:

Here,  will be run and the returned result (called "full" in the   function) will be assigned to the variable "name" in our new function. The greeting part of  will be printed to the screen because that is part of the calculation process. Then, the additional "see you" message will print as well, and the final returned value is back to being.

If you know imperative languages like C, you might think  in Haskell matches   elsewhere. A small variation on the example will dispel that impression:

The string in the extra line will be printed out because  is not a final statement interrupting the flow (as it would be in C and other languages). Indeed, the type of  is , — the type of the final   action. After the function is called, the  created by the   will disappear without a trace.

Just sugar
As a syntactical convenience,  notation does not add anything essential, but it is often preferable for clarity and style. However,  is not needed for a single action, at all. The Haskell "Hello world" is simply:

Snippets like this one are totally redundant:

Thanks to the monad laws, we can write it simply as

A subtle but crucial point relates to function composition: As we already know, the  action in the section just above could be rewritten as:

While you might find the lambda a little unsightly, suppose we had a  function defined elsewhere:

Now, we can have a clean function definition with neither lambdas or :

Or, if we have a non-monadic  function:

Then we can write:

Keep this last example with  in mind; we will soon return to using non-monadic functions in monadic code, and   will be useful there.