Do Notation

In this lesson, we study do-notation, a more convenient way to chain IO actions.

In the last lesson, we learned how to perform IO operations in Haskell and separate them from pure code. We also saw how to combine IO operations in sequence by using the >> and >>= operators. But chaining many IO actions this way tends to look clumsy and unreadable, especially when many >>= operators and lambdas are involved. Luckily, Haskell offers a syntactic sugar variant for this called do-notation.

Introducing do-notation.

As an example, let’s take a simple program that asks the user to enter two numbers and add them, combining several IO actions.

addInput :: IO ()
addInput = putStrLn "What's your first number?" 
                 >> readLn 
                 >>= \first -> (
                   putStrLn "What's your second number?" 
                   >> readLn
                   >>= \second -> putStrLn ("The sum is " ++ (show (first + second)) ++ "!")
                 )

With the nested lambda expressions capturing the output of readLn, this becomes tricky to decipher at first glance. We could split it into several functions to improve readability, but a simpler solution is to use do-notation.

addInput :: IO ()
addInput = do
  putStrLn "What's your first number?" 
  first <- readLn
  putStrLn "What's your second number?" 
  second <- readLn
  putStrLn ("The sum is " ++ (show (first + second)) ++ ".")

To use do-notation, we write do to the right side of a function equation and then put each IO action we want to use on a single line. For IO actions that return an output that we want to use (like getLine), we can bind the output to a variable like in first <- getLine. The String yielded by getLine :: IO String will be bound to first, which can then be used in later IO actions.

Here is a terminal in which you can run the addInput IO action.

Get hands-on with 1400+ tech skills courses.