Reputation: 735
I just started learning Haskell and I briefly read through some of the indentation rules and it seems to me that Haskell behaves just like Python when it comes to indentation (I might be wrong). Anyway, I tried to write a tail recursive fibonacci function and I keep getting an indentation error and I don't know where I indented my code wrong.
ERROR message:
F1.hs:6:9: error:
parse error (possibly incorrect indentation or mismatched brackets)
|
6 | |n<=1 = b | ^
Code:
fib :: Integer -> Integer
fib n = fib_help n 0 1
where fib_help n a b
|n<=1 = b
|otherwise fib_help (n-1) b (a+b)
Note: I am writing the code in Notepad++ and I have changed the settings so that when I TAB it created 4 whitespaces instead of a tab character (like it should be I guess)
Upvotes: 3
Views: 1865
Reputation: 71400
You can imagine Haskell and Python indentation similarly, but there are a couple of small differences.
The biggest difference however is probably that Python's indentation-sensitive syntax always start the aligned block on a new line, whereas Haskell has syntactic constructs with aligned blocks that are permitted to start part way through an existing line. This isn't really a difference in the layout-rules, but it dramatically effects how you think about them (Haskellers don't tend to simplify the rules down to "indent levels" in their heads as much).
Here's an example of some (horrible) layout-sensitive syntax in Python:
if True:
x = 1
while (
not x
):
y = 2
The if
and while
constructs are followed by a suite of aligned statements. The first non-whitespace of character next statement must be indented to some position further than the alignment position of the outer block, and sets the alignment for all following statements in the same inner block. The first character of each statement must align to some position of an enclosing block (which determines which block it's a part of).
If we added a z = 3
at aligned to position 0, it will be part of the global "block". If we add it aligned to position 4 it will be part of the if
block. If we add it aligned to position 5, it will be part of the while
block. Starting a statement at any other position would be a syntax error.
Note also that there are multi-line constructs whose alignment is totally irrelevant. Above I wrote the condition of the while
over multiple lines using parentheses, even aligning the line with not x
to position 0. It doesn't even matter that the colon introducing the indented block was on a "mis-aligned" line; the relevant alignments for the indented block are the position of the first non-whitespace character of the while
statement (position 4), and the position of the first non-whitespace character of the next statement (position 5).
Here's some (horrible) layout-sensitive Haskell:
x = let
y = head . head $ do
_ <- [1, 2, 3]
pure [10]
z = let a = 2
b = 2
in a * b
in y + z
Here we have let
(twice) and do
introducing aligned blocks. The definition of x
itself is part of the "block" of definitions forming the module, and is required to be at position 0.
The first non-whitespace character of the first definition in a let
block sets the alignment of all other definitions in the block. In the outer let
block that's the y
at position 3. However the syntax of let
does not require a line break before beginning the indented block (as Python's indented constructs all do by ending the "header" with a colon and a new line). The inner let
block has a = 2
immediately following the let
, but the position of a
still sets the required alignment for other definitions in the block (11).
Again there's stuff you can split over multiple lines where the lines aren't required to align. In Haskell you can do this with almost anything that isn't a particular layout-sensitive construct, whereas in Python you can only do it with parentheses or ending a line with a backslash. But in Haskell, all of the lines forming part of a construct have to be indented further than the block they're part of. For example, I was able to put the in a * b
part of the definition of z
on a separate line. The in
is part of the let
syntactic construct, but it's not part of the aligned block of definitions introduced by let
, so it has no particular alignment requirements. However the whole definition of z = ...
is part of the outer let
block of definitions, so I could not start the in a * b
line at position 3 or earlier; it's a "continuation line" of the z
definition, so is required to be indented further than the start of that definition. This is different than Python's continuation lines, which have no constraints on their indentation at all.
The do
also introduces an aligned block (of "statements" rather than definitions). I could have followed the first statement immediately on from the do
, but instead I chose to start a new line. The block here behaves much like a Python-style intended block; I had to start it at some position further indented than the outer block (the definitions of the outer let
at position 3), and once I've done that all the statements in the do
block have to be aligned to the same position (14, here). Since the next line after the pure [10]
is z = ...
starting at position 3, it implicitly ends the do
block because it's aligned to the let
block's definitions, not the do
block's statements.
In your example:
fib :: Integer -> Integer
fib n = fib_help n 0 1
where fib_help n a b
|n<=1 = b
|otherwise fib_help (n-1) b (a+b)
The construct requiring alignment is where
, which introduces a block of definitions much like let
. Using Python-style blocks where you always start a new line before beginning a new block, your example would look like this:
fib :: Integer -> Integer
fib n = fib_help n 0 1
where
fib_help n a b
|n<=1 = b
|otherwise fib_help (n-1) b (a+b)
Which makes the error jump out at you much more. You didn't start the block of definitions in the where
at the next "indent level" of 4 spaces, you started it at position 10! And then went back to position 8 for the next "indent level".
If you're more comfortable thinking of Python-style "indent levels" than Haskell-style alignment, simply format your Haskell blocks the way Python requires you to format its blocks; after the "header" introducing a block, always end the line and then begin the block on the next line at the next "tab stop".
Upvotes: 0
Reputation: 85767
No, Haskell indentation is not like Python.
Haskell is not about indentation levels, it's all about making things line up with other things.
where fib_help n a b
In this example you have where
, and the following token is not {
. This activates layout mode (i.e. whitespace sensitive parsing). The next token (fib_help
) sets the start column for the following block:
where fib_help n a b
-- ^
-- | this is "column 0" for the current block
The next line is:
|n<=1 = b
The first token (|
) is indented less than "column 0", which implicitly closes the block.
Your code is parsed as if you had written
fib n = fib_help n 0 1
where { fib_help n a b }
|n<=1 = b
|otherwise fib_help (n-1) b (a+b)
This is several syntax errors: The where
block is missing a =
, and you can't start a new declaration with |
.
Solution: Indent everything that should be part of the where
block more than the first token after where
. For example:
fib n = fib_help n 0 1
where fib_help n a b
|n<=1 = b
|otherwise = fib_help (n-1) b (a+b)
Or:
fib n = fib_help n 0 1
where
fib_help n a b
|n<=1 = b
|otherwise = fib_help (n-1) b (a+b)
Or:
fib n = fib_help n 0 1 where
fib_help n a b
|n<=1 = b
|otherwise = fib_help (n-1) b (a+b)
Upvotes: 10
Reputation: 36375
Two things:
otherwise
still needs an =
sign after it (otherwise
is synonymous with True
):fib :: Integer -> Integer
fib n = fib_help n 0 1
where fib_help n a b
|n<=1 = b
|otherwise = fib_help (n-1) b (a+b)
To say that Haskell indentation is like Python's is probably an overgeneralization, simply because the language constructs are drastically different. A more accurate statement would be to say that whitespace is significant in both Haskell and Python.
Upvotes: 5