Schytheron
Schytheron

Reputation: 735

Is indentation in Haskell like in Python?

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

Answers (4)

Ben
Ben

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

melpomene
melpomene

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

Chad Gilbert
Chad Gilbert

Reputation: 36375

Two things:

  1. You need to line up the pipes to occur after the helper function starts. Here is a good explanation on why this is.
  2. 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

Gober
Gober

Reputation: 167

You missed a "=" in otherwise, you can see more examples here. The right 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)

Upvotes: 0

Related Questions