Since the early 2000s, the `RebindableSyntax`

language extension
was our only choice if we wanted to use do-notation with anything
other than instances of the `Monad`

type class. This design became
particularly unwieldy a few years ago, when we started introducing
linear monads in our experiments with
-XLinearTypes. In this post
we will discuss how `QualifiedDo`

, a new language extension
in the upcoming 8.12 release of GHC, improves the experience of
writing do-notation with *monad-like* types.

## Do-notation

All Haskellers are accustomed to do-notation. Mark Jones introduced it in the Gofer compiler in 1994, and from there it made it into the Haskell language report in 1996. Since then it has become popular, and for a good reason: it makes easy to read a sequence of statements describing an effectful computation.

```
f :: T String
f = do h <- openAFile
s <- readTheFile h
sendAMessage s
closeTheFile h
waitForAMessage
```

One just reads it top-to-bottom. There are no parentheses and no operator precedences to figure out where statements begin and end.

Because monads are the main abstraction for dealing with effectful
computations in Haskell, it made sense to support do-notation for
instances of the `Monad`

type class, which means that the type `T`

above needs to have a `Monad`

instance.

```
class Applicative m => Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
instance Monad T where
...
```

## Monad-like

In the new century, new ideas appeared about how to represent
effectful programs. A growing collection of monad-like types
accumulated for which the `Monad`

type class was an inadequate
abstraction. Such has been the case of indexed monads, graded
monads, constrained monads and, lately, linear monads.

In the case of linear monads, the operations use linear arrows,
which prevents us from using them to implement the standard
`Monad`

type class.

```
{-# LANGUAGE LinearTypes #-}
module Control.Monad.Linear where
-- constraints elided for the sake of discussion
class (...) => Monad m where
return :: a #-> m a
(>>=) :: m a #-> (a #-> m b) -> m b
(>>) :: Monad m => m () #-> m b #-> m b
m0 >> m1 = m0 >>= \() -> m1
```

Until now, the way to use do-notation with
*not-quite-monads* has been to use the `RebindableSyntax`

language
extension. With `RebindableSyntax`

, we need only to bring into scope
the operations that do-notation should use.

```
{-# LANGUAGE RebindableSyntax #-}
import Control.Monad.Linear as Linear
instance Linear.Monad LinearT where
...
f :: LinearT String
f = do h1 <- linearOpenAFile
(h2, Unrestricted s) <- linearReadTheFile h1
linearLiftIO (sendAMessage s)
linearCloseTheFile h2
linearWaitForAMessage
```

Now the compiler will desugar the `do`

block as we want.

```
f :: LinearT String
f = linearOpenAFile Control.Monad.Linear.>>= \h1 ->
linearReadTheFile h1 Control.Monad.Linear.>>= \(h2, Unrestricted s) ->
linearLiftIO (sendAMessage s) Control.Monad.Linear.>>
linearCloseTheFile h2 Control.Monad.Linear.>>
linearWaitForAMessage
```

`RebindableSyntax`

, however, has other effects over the whole
module. Does your module have another `do`

block on a regular monad?

```
sendAMessage s =
do putStr "Sending message..."
r <- post "https://tweag.io/post" s
if statusCode r == 200
then putStrLn "Done!"
else putStrLn "Request failed!"
```

The compiler will complain that there is no instance of `Linear.Monad IO`

.
And indeed, there is not supposed to be. We want the operations of `Monad IO`

here! But aren’t `Prelude.>>`

and `Prelude.>>=`

in scope anymore? No, they’re
not in scope, because `RebindableSyntax`

also has the effect of not importing
the `Prelude`

module implicitly.

If function `sendAMessage`

had a type signature, GHC would complain that
`IO`

is not in scope.

`sendAMessage :: String -> IO ()`

It gets worse:

```
...
if statusCode r == 200
then putStrLn "Done!"
else putStrLn "Request failed!"
...
```

There would be:

- no
`putStrLn`

in scope - no
`fromInteger`

to interpret the literal`200`

- no
`ifThenElse`

function that`-XRebindableSyntax`

mandates to desugar an`if`

expression

To add insult to injury, in the particular case of linear types, there
is not even a correct way to define an `ifThenElse`

function to desugar `if`

expressions. So enabling `-XRebindableSyntax`

together with
`-XLinearTypes`

deprives us from `if`

expressions in linear contexts.

The list of problems does not end here, but the remaining issues are already
illustrated. Each issue has a simple fix, all of which add up to an annoying bunch
the next time we are faced with the decision to introduce `RebindableSyntax`

or do away with do-notation.

But despair no more, dear reader. The days of agony are a thing of the past.

## Qualified do

By enabling the `QualifiedDo`

language extension, we can qualify the
`do`

keyword with a module alias to tell which operations to use in
the desugaring.

```
{-# LANGUAGE QualifiedDo #-}
import qualified Control.Monad.Linear as Linear
instance Linear.Monad LinearT where
...
f :: LinearT String
f = Linear.do -- Desugars to:
h1 <- linearOpenAFile -- Linear.>>=
(h2, Unrestricted s) <- linearReadTheFile h1 -- Linear.>>=
linearLiftIO (sendAMessage s) -- Linear.>>
linearCloseTheFile h2 -- Linear.>>
linearWaitForAMessage -- Linear.>>
sendAMessage :: String -> IO ()
sendAMessage s = do -- Desugars to:
putStr "Sending message..." -- Prelude.>>
r <- post "https://tweag.io/post" s -- Prelude.>>=
if statusCode r == 200
then putStrLn "Done!"
else putStrLn "Something went wrong!"
```

This has the compiler desugar the `Linear.do`

block with any
operations `Linear.>>=`

and `Linear.>>`

which happen to be in scope.
The net result is that it ends up using `Control.Monad.Linear.>>=`

and `Control.Monad.Linear.>>`

. The unqualified `do`

still uses the
`>>=`

and `>>`

operations from the `Prelude`

, allowing different
desugarings of do-notation in a module with ease.

Is that it? Yes! Really? No extra effects on the module!
One can combine `QualifiedDo`

with `ApplicativeDo`

, or `RecursiveDo`

and
even `RebindableSyntax`

. Only the qualified `do`

blocks will be affected.

Every time that a `do`

block would need to reach out for `mfix`

, `fail`

,
`<$>`

, `<*>`

, `join`

, `(>>)`

or `(>>=)`

, `Linear.do`

will use
`Linear.mfix`

, `Linear.fail`

, `Linear.<$>`

, etc.

## The proposal

An extension being this short to explain is the merit of more than a year-long GHC proposal to nail each aspect that could be changed in the design.

We had to consider all sorts of things we could possibly use to qualify
a `do`

block. How about qualifying with a type class name? Or a value
of a type defined with record syntax? Or an expression? And expressions
of what types anyway? And what names should be used for operations in the
desugaring? And should the operations really be imported? And what if
we wanted to pass additional parameters to all the operations introduced
by the desugaring of a given `do`

block?

There is an answer to each of these questions and more in the proposal. We thank our reviewers, with a special mention to Iavor Diatchki, Joachim Breitner, fellow Tweager Richard Eisenberg, and Simon Peyton Jones, who devoted their energy and insights to refine the design.

## What’s next?

Reviewers have suggested during the discussion that other syntactic sugar
could be isolated with qualified syntax as well: list comprehensions, monad
comprehensions, literals, `if`

expressions or arrow notation are candidates
for this.

For now, though, we are eager to see how much adoption `QualifiedDo`

has. The
implementation has recently been merged into GHC,
and we will be able to assess
how well it does in practice. The extension is now yours to criticize or improve.
Happy hacking!

## About the authors

Facundo is a software engineer supporting development and research projects at Tweag. Prior to joining Tweag, he worked in academia and in industry, on a varied assortment of domains, with an overarching interest in programming languages.

Arnaud is Tweag's head of R&D. He described himself as a multi-classed Software Engineer/Constructive Mathematician. He can regularly be seen in the Paris office, but he doesn't live in Paris as he much prefers the calm and fresh air of his suburban town.

If you enjoyed this article, you might be interested in joining the Tweag team.