user3810155
user3810155

Reputation:

Haskell parse error from incorrect indent

Can anyone explain why this is a syntax error?

f =
  f'
  where f' = do
    if True then
      return ()
    else
      return ()

main = f

If I give more indentation to the if block, then it somehow compiles well.

f =
  f'
  where f' = do
        if True then
          return ()
        else
          return ()

main = f

Or I can just separate the where, which I'd normally do.

f =
  f'
  where
    f' = do
      if True then
        return ()
      else
        return ()

main = f

I'm starting a bounty to get a good explanation for the two questions below. (Yes I read the Haskell report. Shame on me for not understanding 10.3 Layout)

  1. Why is the first example an error?
  2. Why isn't the second example an error?

Upvotes: 5

Views: 235

Answers (1)

Sam van Herwaarden
Sam van Herwaarden

Reputation: 2361

The rule you violate is explained in Note 1 under Section 10.3. Let me quote:

Note 1. A nested context must be further indented than the enclosing context (n > m). If not, L fails, and the compiler should indicate a layout error. An example is: [example, see below] Here, the definition of p is indented less than the indentation of the enclosing context, which is set in this case by the definition of h.

Where the example is the following:

f x = let  
         h y = let  
  p z = z  
               in p  
      in h

In your case, the context of the if-statement, is the f' definition. (Just as in the example, the context of the definition of p is the definition of h.) Since the if-statement is a nested context, it needs to be indented further than the enclosing context (i.e. if needs to be indented further than f'). This is why you get an error in your first snippet.

In your third example p is indented further than f', this is in accordance with the rules.


Edit:

What's strange is that your second example does not give an error since p is not indented further than f', but has the same indentation, and it almost seems like this is not intentional (i.e. a bug). I did some research, and found out that the do statement actually makes the difference. The following gives an error:

f =
  f'
  where f' = -- no do
        if True then
          return ()
        else
          return ()

The interaction between where/let and do seems to cause complications. Check these examples:

1) This compiles without problems:

f = let y = do
        Just 1
    in y

2) This causes an error:

f = let y =
        Just 1
    in y

3) This compiles without problems:

f = do
 Just 1

4) This causes an error:

f = do
Just 1

What's strange here is that the error in (4) is inconsistent with the successful parse of (1), and the successful parse of (1) seems inconsistent with the rule stated earlier (nested contexts need to be further indented).

Edit 2:

Alright, it seems I have gotten to the bottom of it! (I went ahead and filed a bug report, which was kindly marked invalid with an explanation by E.Z. Yang.)

Here is the deal: in some situations it is desirable when using do-notation to not have to constantly increase the indentation. For this reason, the language extension NondecreasingIndentation was implemented, which is on by default. See here for more information.

This feature can be turned off, such that the following version of your code would not compile until you increase the indentation of the if-then-else-part:

{-# LANGUAGE NoNondecreasingIndentation #-}

f =
  f'
  where f' = do
        if True then
          return ()
        else
          return ()

The NondecreasingIndentation feature is only active in contexts which are already indented. This is why my last example (4), even with the extension enabled, does not compile.

TL;DR

The golden rule of indentation generally holds, with an exception. Indented do-blocks do not need to be further indented than their enclosing context. To stop GHC from treating do-blocks differently in this way, enable the language extensions NoNondecreasingIndentation.

Upvotes: 5

Related Questions