2

Haskell report 2010 says

A do expression provides a more conventional syntax for monadic programming. It allows an expression such as

putStr "x: " >>
getLine >>= \l ->
return (words l)

to be written in a more traditional way as:

do putStr "x: "
   l <- getLine
   return (words l)

Haskell the Craft of Functional programming by Thompson says

We'll continue to use the do notation, but will keep in mind that it essentially boils down to the existence of a function (>>=) which does the work of sequencing I/O programs, and binding their results for future use.

Do the above mean that do notation is used necessarily in the context of monad?

If yes, why does the following functor use the do notation?

instance    Functor IO  where
    --  fmap    ::  (a  ->  b)  ->  IO  a   ->  IO  b
    fmap    g   mx  =   do  {x  <-  mx; return  (g  x)}
AJF
  • 11,767
  • 2
  • 37
  • 64
Tim
  • 1
  • 141
  • 372
  • 590
  • 2
    Yes. `do` notation, as mentioned, is simply syntactic sugar for monad operations. In your example with the `Functor` instance for `IO`, we have already defined the monad operations on `IO` and are using them to define `fmap`. – AJF Jul 24 '19 at 23:53
  • 1
    Well there is also `ApplicativeDo`, a GHC extension which allows do notation to be used with Applicatives. I've never tried using it myself but I'm sure I read somewhere that it might be added to the language officially whenever they produce the next report. – Robin Zigmond Jul 24 '19 at 23:58
  • @RobinZigmond, I hope not. It's not really the best behaved of extensions. But perhaps it can be cleaned up. – dfeuer Jul 25 '19 at 03:55

2 Answers2

5

Yes. As the article quoted states, do-notation is simply syntactic sugar for the monad operations.

These are the rules for de-sugaring do-notation:

  1. do {foobar; ...} = foobar >> do {...} (aka foobar >>= \_ -> do {...})
  2. do {a <- foobar; ...} = foobar >>= \a -> do {...}
  3. do {foobar} = foobar

Necessarily this means that do-notation works entirely on monads, except in the trivial case that rule 3 describes.

So for instance, as the article states, do {putStr "x: "; l <- getLine; return (words l)} is exactly equal to putStr "x: " >> (getLine >>= \l -> return (words l)), which you may confirm via the de-sugaring rules.

In the definition for Functor IO you quote above, the Monad IO instance has already been defined, and so we are using it to define the Functor instance, too.

It may also be useful to note that all monads are necessarily functors (see the definition of the Monad typeclass), so when one says do-notation works on monads, it also necessarily works on the functors as well. I suspect this may be a point of confusion.


It is worth noting that in certain restricted cases, it is possible to use only Applicative operations rather than the more general Monad operations. For instance, the example the article provides could be written as putStr "x: " *> (pure words <*> getLine). There is an experimental language extension called ApplicativeDo which adds to GHC the capability to recognise these situations and generalise certain cases of do-notation to all applicatives, not just all monads.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
AJF
  • 11,767
  • 2
  • 37
  • 64
  • Thanks. How do you know "In the definition for Functor IO you quote above, the Monad IO instance has already been defined", i.e. what is defined before what? – Tim Jul 25 '19 at 00:15
  • 3
    @Tim I know it in a holistic way because they're using `do`-notation to define `fmap`! However, in general there's no way of measuring the order of definition in Haskell, since mutual recursion is possible. Rather, I mean that the Monad instance is defined, and in a way that does not refer to `fmap` recursively. I can discover this by looking up the definition on [hoogle](https://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html#t:IO). – AJF Jul 25 '19 at 00:18
  • 1
    Look at rule 3 a bit more closely. It's a purely syntactic desugaring. `do ()` is valid, and not monadic. It's just not very useful. – Carl Jul 25 '19 at 00:35
  • @Carl Thanks for pointing out that edge case. I've added a little bit about that. I actually had to check with GHCi that `do ()` is indeed valid! – AJF Jul 25 '19 at 00:41
  • 1
    @AJFarmar Additionally, if you do `do { one_expr }`, that isn’t monadic either. That may seem like an edge case, but I’ve seen a suggestion that you could use it when `$` isn’t quite right e.g. doing `(,) <$> do long_exp_number_1 {-newline-} <*> do long_exp_number_2`. I’ll link to it if I can find the original advice. – bradrn Jul 25 '19 at 00:52
2

Well, with RebindableSyntax extension it will use whatever (>>=), (>>), and fail are in scope. So technically Monad is not required in such case and you can do something as silly as:

{-# LANGUAGE RebindableSyntax #-}

a >>= b = b a
return a = a

-- test == ("foo","boo")
test = do
  a <- "foo"
  b <- "boo"
  return (a,b)
Ed'ka
  • 6,595
  • 29
  • 30