Do notationLanguage reference
Do notation is syntactic sugar for writing code using monads, eventually being replaced by uses of >>
or >>=
by the compiler.
Example
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)
When we run this function in GHCi we get the following:
λ> helloThere
Hello, what is your name?
Haskeller Harry
Hello there Haskeller Harry
λ>
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 (
()
).
- 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 ab
, and returns that value.
- The then operator: perform an IO action that gives you an
-
>>= :: 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 ab
, and return that value.
- The bind operator: perform an IO action that gives you an
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:
- The first thing we do is write a string to the console (via
putStrLn
), this doesn't give us anything meaningful back, just()
. - The
>>
operator discards the value of its first argument (the()
fromputStrLn
) and then performs the next operation:getLine
. getLine
waits for user input, and when it gets it provides it as aString
.- The
>>=
operator takes the value from its first argument (theString
produced bygetLine
) and passes it to the function given as the second argument, in this case the lambda function which takes one argument,name
, and then callsputStrLn
to output the greeting.
This example used IO, but it works for all monads.
More Resources
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 return
s 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