Good Haskell coding style of if/else control block?

coding-style, haskell

Solution

Haskell style is functional, not imperative! Rather than "do this then that," think about combining functions and describing what your program will do, not how.

In the game, your program asks the user for a guess. A correct guess is a winner. Otherwise, the user tries again. The game continues until the user guesses correctly, so we write that:

main = untilM (isCorrect 42) (read `liftM` getLine)

This uses a combinator that repeatedly runs an action (`getLine` pulls a line of input and `read` converts that string to an integer in this case) and checks its result:

untilM :: Monad m => (a -> m Bool) -> m a -> m ()
untilM p a = do
  x <- a
  done <- p x
  if done
    then return ()
    else untilM p a

The predicate (partially applied in `main`) checks the guess against the correct value and responds accordingly:

isCorrect :: Int -> Int -> IO Bool
isCorrect num guess =
  case compare num guess of
    EQ -> putStrLn "You Win!"  >> return True
    LT -> putStrLn "Too high!" >> return False
    GT -> putStrLn "Too low!"  >> return False

The action to be run until the player guesses correctly is

read `liftM` getLine

Why not keep it simple and just compose the two functions?

*Main> :type read . getLine

<interactive>:1:7:
    Couldn't match expected type `a -> String'
           against inferred type `IO String'
    In the second argument of `(.)', namely `getLine'
    In the expression: read . getLine

The type of `getLine` is `IO String`, but `read` wants a pure `String`.

The function `liftM` from Control.Monad takes a pure function and “lifts” it into a monad. The type of the expression tells us a great deal about what it does:

*Main> :type read `liftM` getLine
read `liftM` getLine :: (Read a) => IO a

It's an I/O action that when run gives us back a value converted with `read`, an `Int` in our case. Recall that `readLine` is an I/O action that yields `String` values, so you can think of `liftM` as allowing us to apply `read` “inside” the `IO` monad.

Sample game:

1
Too low!
100
Too high!
42
You Win!

Problem

I'm learning Haskell in the hope that it will help me get closer to functional programming. Previously, I've mostly used languages with C-like syntax, like C, Java, and D. I have a little question about the coding style of an `if`/`else` control block used by the tutorial on Wikibooks. The code looks like the following: ``` doGuessing num = do putStrLn "Enter your guess:" guess <- getLine if (read guess) < num then do putStrLn "Too low!" doGuessing num else if (read guess) > num then do putStrLn "Too high!" doGuessing num else do putStrLn "You Win!" ``` It makes me confused, because this coding style totally violates the recommended style in C-like languages, where we should indent `if`, `else if`, and `else` at the same column. I know it just does not work in Haskell, because it would be a parse error if I indented `else` at the same column as `if`. But what about the following style? I think it is much more clear than the above one. But since the above is used by Wikibooks and Yet Another Haskell Tutorial, which is marked "best tutorial available online" at the official Haskell website, I'm not sure whether this coding style is a convention in Haskell programs. ``` doGuessing num = do putStrLn "Enter your guess:" guess <- getLine if (read guess) < num then do putStrLn "Too low!" doGuessing num else if (read guess) > num then do putStrLn "Too high!" doGuessing num else do putStrLn "You Win!" ``` So, I'm curious about which coding style is used more often—or is there another coding style for this piece of code?

Original source