Reputation:
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)
Upvotes: 5
Views: 235
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 ofh
.
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