Search code examples
haskellletdo-notation

Monadic do notation inside let, is it possible?


Consider the following valid Haskell code

module Main where

main :: IO ()
main = do
  let x = f
  print x

f :: Maybe (Int, Int)
f =
  Just 3 >>= (\a ->
    Just 5 >>= (\b ->
      return (a, b)))

where the function f can be rewritten equivalently with do-notation like this

f :: Maybe (Int, Int)
f = do
  a <- Just 3
  b <- Just 5
  return (a, b)

What annoys me, the do notation won't work when I put the contents of f inline. The following code does not even parse:

main :: IO ()
main = do
  let x = do
    a <- Just 3
    b <- Just 5
    return (a, b)
  print x

Am I correct that inside let I am forced to resort to (>>=)?

While I'm at it, the following code does not parse, either:

module Main where

main :: IO ()
main = do
  let x =
    Just 3 >>= (\a ->
      Just 5 >>= (\b ->
        return (a, b)))
  print x

I don't see an obvious reason other than an unnecessary limited power of let. Is there an elegant way to use bind inside let?


Solution

  • Am I correct that inside let I am forced to resort to (>>=)?

    No:

    main :: IO ()
    main = do
      let x = do
          a <- Just 3
          b <- Just 5
          return (a, b)
      print x
    

    Haskell's layout rule dictates that the body of the binding e in p = e mus be intended at least as much as the beginning of p (or the first binding, if you're using multiple at once). Since let in do follows (almost) the same rules as let … in, you can verify this with the following function:

    f :: Int
    f = 
      let x =
        3 + 5
      in x
    

    This doesn't work, since 3 + 5 doesn't have the same or greater indentation level as x. However,

    f :: Int
    f =
      let x =
           3 + 5
      in x
    

    works. Also, while the main above works, it doesn't really convey that a and b are things in x's do block, so it's a little bit better to indent them slightly more:

    main :: IO ()
    main = do
      let x = do
            a <- Just 3
            b <- Just 5
            return (a, b)
      print x