6

As far as I know, do blocks in Haskell are just some kind of syntactic sugar for monadic bind operators. For example, one could convert

main = do f <- readFile "foo.txt"
          print f
          print "Finished"

to

main = readFile "foo.txt" >>= print >> print "Finished"

Can all do blocks be converted to bind syntax? What about, for example, this block where f is used multiple times:

main = do f <- readFile "foo.txt"
          print $ "prefix " ++ f
          print $ f ++ " postfix"

Assuming we are in the IO monad, it is not possible to simply execute the readFile computation twice. How can this example (if possible at all) expressed using only bind syntax?

I think using Control.Monad is no solution, because it internally uses do blocks.

I think it it's possible to express this using arrows (using &&&) -- maybe this is a case where only arrows can be used as a generalization of monads?

Note that this question is not about the special examples above but about the general case of the result of a computation being used multiple times in monadic expressions like print.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Uli Köhler
  • 13,012
  • 16
  • 70
  • 120
  • 1
    Often this makes your code prettier. `Control.Applicative` has a bunch of operators you can use to mess with stuff of type `m a`, then you can `>>=` to the output. Usually that's much prettier. Sometimes point free isn't prettier at all; we could write `main = readFile "foo.txt" >>= flip mapM_ [("prefix" ++), (++ "postfix")] . (print .) . flip id` but that wouldn't be very clear. – AndrewC Feb 22 '14 at 16:58
  • @AndrewC Thanks for your suggestion! In this particular case I was only interested in how to convert the do syntax and I intended to keep it simple. Generally, I fully agree to you that applicative is the correct tool here. Besides, is there any specific reason why you don't use `forM_` instead of `flip mapM_`? – Uli Köhler Feb 22 '14 at 17:04
  • 2
    Because I forgot it! :) Let's pretend it was for another reason like avoiding importing `Control.Monad`. Trouble with that is that if you don't import `Control.Monad`, you miss the lovely `(>=>) :: Monad m => (a->m b) -> (b->m c) -> (a -> m c)`. – AndrewC Feb 22 '14 at 18:02

2 Answers2

12

Yes, all of them can be converted to bind syntax; in fact, they are converted internally by the compiler.

I hope this translation of your example gives you the hint:

main = readFile "foo.txt" >>= \f ->
       (print $ "prefix " ++ f) >>
       (print $ f ++ " postfix")
MigMit
  • 1,698
  • 12
  • 14
8

The Report gives a full translation from do syntax into kernel Haskell:

Do expressions satisfy these identities, which may be used as a translation into the kernel, after eliminating empty stmts:

do {e}                = e
do {e;stmts}          = e >> do {stmts}
do {p <- e; stmts}    = let ok p = do {stmts}
                            ok _ = fail "..."
                        in e >>= ok
do {let decls; stmts} = let decls in do {stmts}

The ellipsis "..." stands for a compiler-generated error message, passed to fail, preferably giving some indication of the location of the pattern-match failure; the functions >>, >>=, and fail are operations in the class Monad, as defined in the Prelude; and ok is a fresh identifier.

So your example translates this way:

do f <- readFile "foo.txt"
   print $ "prefix " ++ f
   print $ f ++ " postfix"
=
let ok f = do print $ "prefix " ++ f
              print $ f ++ " postfix"
    ok _ = fail "..."
in readFile "foo.txt" >>= ok
=
let ok f = (print $ "prefix " ++ f) >> do print $ f ++ " postfix"
    ok _ = fail "..."
in readFile "foo.txt" >>= ok
=
let ok f = (print $ "prefix " ++ f) >> (print $ f ++ " postfix")
    ok _ = fail "..."
in readFile "foo.txt" >>= ok

This version has no do blocks, but doesn't look very natural. But we can apply equational reasoning and whatever optimizations we know. So, for example, observing that the ok _ = fail "..." clause is dead code, we could inlike ok like so:

 =
 readFile "foo.txt" >>= \f ->
 (print $ "prefix " ++ f) >>
 (print $ f ++ " postfix")

All do blocks can be mechanically translated into code without do in this way.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380