views:

540

answers:

3

This the entire program:

import Data.Char (digitToInt)

myInt :: String -> Int
myInt [] = error "bad input: empty string"
myInt (x:xs)
  | x == '-'  = -1 * myInt xs
  | otherwise = foldl convert 0 (x:xs)
  where convert acc x
        | x `elem` ['0'..'9'] = 10 * acc + digitToInt x
        | otherwise           = error ("bad input: not an int - " ++ [x])

Fails:

Prelude> :l safeListFs.hs
[1 of 1] Compiling Main             ( safeListFs.hs, interpreted )

safeListFs.hs:9:8: parse error (possibly incorrect indentation)
Failed, modules loaded: none.

But this version:

import Data.Char (digitToInt)

myInt :: String -> Int
myInt [] = error "bad input: empty string"
myInt (x:xs)
  | x == '-'  = -1 * myInt xs
  | otherwise = foldl convert 0 (x:xs)
  where convert acc x
          | x `elem` ['0'..'9'] = 10 * acc + digitToInt x
          | otherwise           = error ("bad input: not an int - " ++ [x])

is ok:

Prelude> :l safeListFs.hs
[1 of 1] Compiling Main             ( safeListFs.hs, interpreted )
Ok, modules loaded: Main.

I can't figure out why those two last indents matter.

+5  A: 

Because you should always indent function definitions. (In your case, all things started at same column in where are considered "same-level" definition).

Alexander Poluektov
Offtopic: I have a friend whose name is Andrei Poluektov
artemave
I still don't understand this answer, it seems in both examples **where** is right under the pipe ('|') and indented the same
Evan Carroll
@Evan, 'where' is properly indented in both cases. It is pipes after 'where' which produce the the error. Because they aren't properly indented against 'convert' (in the first example)
artemave
+10  A: 

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.

From http://www.haskell.org/onlinereport/syntax-iso.html.

This would also fail:

import Data.Char (digitToInt)

myInt :: String -> Int
myInt [] = error "bad input: empty string"
myInt (x:xs)
| x == '-'  = -1 * myInt xs
| otherwise = foldl convert 0 (x:xs)
where convert acc x
        | x `elem` ['0'..'9'] = 10 * acc + digitToInt x
        | otherwise           = error ("bad input: not an int - " ++ [x])

Uh, I'm bad at explaining things. There's a new context after where keyword, because you can specify more than one function in there -- remember that your program begins with implicit module Main where, so I think it's logical to require function body to be indented, just like on the module level (compiler expects another identifier on columns M and N, and declaration bodies to be further indented).

fun = ...
^ where fun' = ...
M       ^
        N
        fun'' = ...
fun2 = ...
PiotrLegnica
Although your answers is obviously correct, it doesn't target my particular confusion. Which was based on the assumption that nested scope indentation would start from 'where'. Whereas it actually starts from function name.
artemave
I still don't understand this: speak to why the OP's example that works, works, and why the example that doesn't work, doesn't.
Evan Carroll
@Evan, see my edit. This is how I understand it.
PiotrLegnica
I'm still not sure I understand this: the function is under the module scope with 0 indents, but the where is under the module scope with 1 indent?
Evan Carroll
+5  A: 

Basically, because Haskell notes the column where the first non-space character after where appears (in this case, the c of convert) and treats following lines beginning in that column as new definitions inside the where.

A line that continues the definition of the previous line (such as your | guards) must be indented to the right of c, as in your version that works.

A line indented to the left of c (there aren't any in your example) would be outside the where (for example, the start of your next top-level function).

It's the column of the first character following where that is crucial, even if it's on a new line:

  where
    convert acc x
      | ...
    anotherFunction x y

    ^ 
Nefrubyr
++ Wonderful, I couldn't jump on board of the other answers, this is much like what @arternave suggested recently in a comment, I think this explains the issue.
Evan Carroll