Haskell IO indentation

181 Views Asked by At

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

2

There are 2 best solutions below

0
On BEST ANSWER

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.

0
On

Another option is in-line pattern matching for sum types. This is nice if you have a short bit of code and you do not want to use multiple lines.

z <- maybeDoSomething :: IO (Maybe Int)
let x = case z of { Nothing -> 0; Just v -> v }

It can also short the space required for pattern matching in anonymous functions. This:

(\x -> case t of
         Nothing -> 0
         Just v  -> v
)

Can be changed to this:

(\x -> case t of { Nothing -> 0; Just v  -> v })

You can also avoid if-then-else.

t <- didSomethingSucceed :: IO Bool
let x = case t of { True -> 1; False -> 0 }

I would only use this if the lines can be kept short and you have a small number of patterns to match, otherwise it might be hard to read.