What indentation is required for a case statement within a let statement?
Asked Answered
N

2

12

Working in haskell, found odd behavior, stripped it down to bare bones

This Works

a :: Bool
a = case True of
    True -> True
    False -> False

But when I try

b :: IO Bool
b = do
    let b' = case True of
        True -> True
        False -> False
    return b'

I get

ghci>:l test.hs
[1 of 1] Compiling Main             ( test.hs, interpreted )

test.hs:16:14: parse error on input ‘->’
Failed, modules loaded: none.

So I try

c :: IO Bool
c = do
    let c' = case True of
            True -> True
            False -> False
    return c'

And this works.

What? Why? Why do I need an extra indent in this case? I can't find anything on this, probably because these keyword are so short and common in everyday language. Is there some spec that explains this behavior?

Nonrigid answered 8/10, 2015 at 0:12 Comment(1)
The indentation level of the let block starts at the first non-whitespace character after let. So True is being interpreted as a clause in the let block; one more space will get you inside the case blockAbstract
C
9

I don't have the exact wording from the spec, but this Wikibook page explains the issue quite clearly.

The reason why it works like this is simple: to support binding multiple variables via a single let-group, such as:

c = do
    let c' = …
        d  = …
        e  = …
    return c'

Your True -> … and False -> … are mistakenly interpreted as additional variables to be bound.

Costmary answered 8/10, 2015 at 0:15 Comment(3)
And here I thought the capitalization of Data constructors was meant to remove ambiguities like this. I suppose that's what I get for trying to work with python and other languages at the same timeNonrigid
"capitalization of Data constructors was meant to remove ambiguities like this" You can't rely on that in this case. What if you had a pattern match like: case … of { x -> …; _ -> …; } ? In this case x and _ are both valid variable names. (Contrived example, I know, but still.)Costmary
And the other way as well, let Just f = ... is a perfectly valid declaration.Persis
C
18

The basic indentation rules are actually quite simple:

  • after the keywords which start a block (where,let,do,case .. of) note down the column where the next word starts (which might be in the next line)
  • lines indented exactly as that are new entries in the block
  • lines indented more than that continue the previous entry
  • a line indented less than that ends the block right before that line
  • in nested blocks, apply the rules to the outermost block, first

Tricky example:

1 + case x of
      A -> 45  -- note where "A" starts
      B -> 10  -- same indentation: another case branch
       + 2     -- more indented, so it's "10+2"
     + 10      -- less indented, so it's "1+(case ...)+10"

In your case,

let b' = case True of
    True -> True
    False -> False

we have two nested blocks, one for let and one for case..of. The let blocks uses the column of b'. The case..of block tries to reuse the same column, but we need to apply the rules the the outermost block, first. So the True -> ... line is actually a new entry of the let block. This triggers a parsing error.

Cassock answered 8/10, 2015 at 8:42 Comment(2)
As a beginner to Haskell, I do not think the indentation rules are simple, however, this is the best explanation on them I have come across. I've bookmarked this answer and expect to keep coming back to it :)Belgium
@jrahhali If everything fails, you can postpone learning the indentation rules and use explicit braces-and-semicolons, e.g. case x of { True -> ... ; False -> ... } and let { x = ... ; y = ... ; z = ... } in .... In such way, indentation no longer matters. This is not a common style in Haskell, since it's more noisy, but perhaps for a beginner it helps being more explicit. You can learn the exact indentation rules after you mastered the important parts of Haskell.Cassock
C
9

I don't have the exact wording from the spec, but this Wikibook page explains the issue quite clearly.

The reason why it works like this is simple: to support binding multiple variables via a single let-group, such as:

c = do
    let c' = …
        d  = …
        e  = …
    return c'

Your True -> … and False -> … are mistakenly interpreted as additional variables to be bound.

Costmary answered 8/10, 2015 at 0:15 Comment(3)
And here I thought the capitalization of Data constructors was meant to remove ambiguities like this. I suppose that's what I get for trying to work with python and other languages at the same timeNonrigid
"capitalization of Data constructors was meant to remove ambiguities like this" You can't rely on that in this case. What if you had a pattern match like: case … of { x -> …; _ -> …; } ? In this case x and _ are both valid variable names. (Contrived example, I know, but still.)Costmary
And the other way as well, let Just f = ... is a perfectly valid declaration.Persis

© 2022 - 2024 — McMap. All rights reserved.