category feedDo notationLanguage referenceeditdelete

This category is a work in progress

Do notation is syntactic sugar for writing code using monads, eventually being replaced by uses of >> or >>= by the compiler.


Lets first look at an example that uses do notation to sequence input and output operations.

helloThere :: IO ()
helloThere = do
  putStrLn "Hello, what is your name?"
  name <- getLine
  putStrLn ("Hello there " ++ name)

This is a function in the IO monad, meaning that computations require interacting with the outside world (in this case writing to and reading from the terminal). The function prints Hello, what is your name? to the terminal, then waits for the user to enter their name (a String), then greets them with Hello there <name>.

Lets look at the types of the functions we use and the >> and >>= operators (specialized to IO):

  • putStrLn :: String -> IO ()

    • Write a string to the console, performing the action doesn't hive you anything meaningful back (()).
  • getLine :: IO String

    • Read a line of input from the console, the action produces the string that is read from the console.
  • >> :: IO a -> IO b -> IO b

    • The then operator: perform an IO action that gives you an a but ignore the output, then perform another IO action that gives you a b, and returns that value.
  • >>= :: IO a -> (a -> IO b) -> IO b

    • The bind operator: perform an IO action that gives you an a, take that value and apply a function (the second argument) which performs an IO action that gives you a b, and return that value.

The name <- getLine syntax means: "run the getLine IO action which produces a String, and assign that string to the symbol name to be used later in the do block". Notice in the de-sugared version version below how the variable binding is transformed into >>= and a lambda.

The code above is de-sugared to the following:

helloThereDesugared :: IO ()
helloThereDesugared =
  putStrLn "Hello, what is your name?" >>
  getLine >>= (\name -> 
    putStrLn ("Hello there " ++ name))

Walking through the de-sugared version:

  1. The first thing we do is write a string to the console (via putStrLn), this doesn't give us anything meaningful back, just ().
  2. The >> operator discards the value of its first argument (the () from putStrLn) and then performs the next operation: getLine.
  3. getLine waits for user input, and when it gets it provides it as a String.
  4. The >>= operator takes the value from its first argument (the String produced by getLine) and passes it to the function given as the second argument, in this case the lambda function which takes one argument, name, and then calls putStrLn to output the greeting.

This example used IO, but it works for all monads.

More Resources

edit description
or press Ctrl+Enter to savemarkdown supported
When NOT to use do
move item up move item down edit item info delete item
Summary edit summary

There are a number of situations where using do notation doesn't buy you anything and only results in less concise code. The sections below contain pairs of code blocks that are equivalent where the latter should be preferred.

Single statement do blocks

A do block with a single statement s is equivalent to s on its own.

oneLineDo name = do
  putStrLn ("Hi there " ++ name)
oneLineDoSimplified name = putStrLn ("Hi there " ++ name)

Assignment then return

If the second to last line in a do block is a variable binding (x <- bar) and the last line is return x, they can be replaced by simply bar.

assignAndReturn = do
  x <- bar
  return x
assignAndReturnSimplified = bar

Only bind needed

If the do block consists of a sequence of variable binding via x <- followed by passing x to another function, these can be replaced by monadic function composition using the bind operator >>=.

onlyBindNeeded = do
  x <- getX
  y <- getY x
  return y
onlyBindNeededSimplified = getX >>= getY

Only lift needed:

If the do block consists of a number of variable binding via <- and the last line returns a value constructed from the bindings, prefer using the liftA<n> functions (if your <n> is small) or using applicative style via <$> and <*>.

onlyLiftNeeded :: IO (a, b)
onlyLiftNeeded = do
  a <- getA
  b <- getB
  return (a, b)
onlyLiftNeededLiftA2 :: IO (a, b)
onlyLiftNeededLiftA2 = liftA2 (,) getA getB
onlyLiftNeededApplicativeStyle :: IO (a, b)
onlyLiftNeededApplicativeStyle = (,) <$> getA <*> getB
Summary quit editing summary