haskellioguard-statement

Haskell IO indentation


I tried to rewrite that program, which is working:

nameIOite :: IO ()
nameIOite = do
    putStrLn "What's your name ?"
    name <- getLine
    if name `elem` ["Simon","John","Phil"]
  --if name == "Simon" || name == "John" || name == "Phil" also works but is ugly.   
        then putStrLn "I think Haskell is a great programming language."
        else if name == "Koen"
            then putStrLn "I think debugging Haskell is fun."
            else putStrLn "I don't know your name."

This is done using if/then/else (therefore the suffix ite in nameIOite)

Then I'vs tried using guards:

nameIOg :: IO ()
nameIOg = do
    putStrLn "What's your name ?"
    name <- getLine
    let answer
        | name `elem` ["Simon","John","Phil"]   = "I think Haskell is a great programming language."
        | name == "Koen"                        = "I think debugging Haskell is fun."
        | otherwise                             = "I don't know your name."
    putStrLn answer

This didn't work:

test.hs:6:9: error:
parse error (possibly incorrect indentation or mismatched brackets)
  |
6 |    | name `elem` ["Simon","John","Phil"]   = "I think Haskell is   a great programming language."
  |    ^
Failed, no modules loaded.

After some experimenting, the solution turned out to indent the guards once more (which isn't clear to me at all):

nameIOg :: IO ()
nameIOg = do
    putStrLn "What's your name ?"
    name <- getLine
    let answer
            | name `elem` ["Simon","John","Phil"]   = "I think Haskell is a great programming language."
            | name == "Koen"                        = "I think debugging Haskell is fun."
            | otherwise                             = "I don't know your name."
    putStrLn answer

Ok, one module loaded.

Where does that double indentation come from and is there a way to write this more elegently ?

(By the way, I stumbled across this when reviewing my wikibook.hs files.)

Source of example: there

Solutions: there


Solution

  • let allows multiple definitions, as in

    main = do
       doSomething
       let x = 1
           y = 2
           z = 3
       print (x+y+z)
    

    Note the indentation. y = 2 is not parsed to continue the definition x = 1 since it starts on the same column.

    If you want a new line to be parsed as if it continued the previous line, you have to indent it more. E.g.

    main = do
       doSomething
       let x | someCondition = 1
             | otherwise     = 0   -- more indented
           y = 2
           z = 3
       print (x+y+z)
    

    or, using another line

    main = do
       doSomething
       let x
              | someCondition = 1   -- more indented
              | otherwise     = 0   -- more indented
           y = 2
           z = 3
       print (x+y+z)
    

    Indentation rules might seem puzzling at first, but they are actually quite simple.

    I think your current code is as elegant as it might be -- it looks fine to me.

    If you want more alternatives, you could use if then else, even if most Haskellers would prefer guards. (Personally, I don't have a real preference)

    main = do
       doSomething
       let x = if condition               then 1
               else if someOtherCondition then 0
               else                            -1
           y = 2
           z = 3
       print (x+y+z)
    

    You could also use another line, e.g. (I'd tend to prefer that)

    main = do
       doSomething
       let x =
              if condition               then 1
              else if someOtherCondition then 0
              else                            -1
           y = 2
           z = 3
       print (x+y+z)
    

    or even

    main = do
       doSomething
       let x =
              if condition
              then 1
              else if someOtherCondition
              then 0
              else -1
           y = 2
           z = 3
       print (x+y+z)
    

    I'm not claiming a style is overwhelmingly better than another one.