30

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?

0

8 Answers 8

28

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!
2
  • 2
    Nice work on the use of the combinators. I would go a little bit further and write 'untilM (isCorrect 42) (read <$> getLine)' and 'True $> putStrLn "You Win!"'.
    – Martijn
    Commented Jan 19, 2010 at 21:40
  • @Martijn Thanks for the suggestions! Looks like you meant <$ on the latter: haskell.org/ghc/docs/6.12.1/html/libraries/base-4.2.0.0/…
    – Greg Bacon
    Commented Jan 19, 2010 at 22:24
8

You can use the "case"-construct:

doGuessing num = do
    putStrLn "Enter your guess:"
    guess <- getLine
    case (read guess) of
        g | g < num -> do 
            putStrLn "Too low!"
            doGuessing num
        g | g > num -> do 
            putStrLn "Too high!"
            doGuessing num
        otherwise -> do 
            putStrLn "You Win!"
0
8

A minor improvement to mattiast's case statement (I'd edit, but I lack the karma) is to use the compare function, which returns one of three values, LT, GT, or EQ:

doGuessing num = do
   putStrLn "Enter your guess:"
   guess <- getLine
   case (read guess) `compare` num of
     LT -> do putStrLn "Too low!"
              doGuessing num
     GT -> do putStrLn "Too high!"
              doGuessing num
     EQ -> putStrLn "You Win!"

I really like these Haskell questions, and I'd encourage others to post more. Often you feel like there's got to be a better way to express what you're thinking, but Haskell is initially so foreign that nothing will come to mind.

Bonus question for the Haskell journyman: what's the type of doGuessing?

3
  • type? excellent question. I'll give a few hints, but not the answern. The input parameter is called num, but must it be numeric? We might guess Int (a very specific type), but what contstraints are there on the input parameter? As for the output, some hinsts are the do keyword, and the putStrLn.
    – ja.
    Commented Nov 12, 2008 at 8:24
  • Int -> IO (), as far as I can tell.
    – Rayne
    Commented Jan 19, 2010 at 21:41
  • 1
    As ja pointed out, num doesn't have to be numeric. The only constraint is that it's used with compare, telling us it must be an instance of Ord. It must be the same type as (read guess), making it an instance of Read (i.e. something you can read from a String). So I make it doGuessing :: (Read a, Ord a) => a -> IO () (though I haven't checked).
    – Nefrubyr
    Commented Jan 20, 2010 at 11:05
4

The way Haskell interprets if ... then ... else within a do block is very much in keeping with the whole of Haskell's syntax.

But many people prefer a slightly different syntax, permitting then and else to appear at the same indentation level as the corresponding if. Therefore, GHC comes with an opt-in language extension called DoAndIfThenElse, which permits this syntax.

The DoAndIfThenElse extension is made into part of the core language in the latest revision of the Haskell specification, Haskell 2010.

1
  • Is this already supported by GHC, or is it just a proposal? I couldn't find the pragma/flag in the manual.
    – Wei Hu
    Commented Feb 9, 2010 at 8:00
3

Note that the fact that you have to indent the 'then' and 'else' inside a 'do' block is considered a bug by many. It will probably be fixed in Haskell' (Haskell prime), the next version of the Haskell specification.

0
1

You can also use explicit grouping with curly braces. See the layout section of http://www.haskell.org/tutorial/patterns.html

I wouldn't recommend that though. I've never seen anyone use explicit grouping besides in a few special cases. I usually look at the Standard Prelude code for examples of style.

0

I use a coding style like your example from Wikibooks. Sure, it doesn't follow the C guidelines, but Haskell's not C, and it's fairly readable, especially once you get used to it. It's also patterned after the style of algorithms used in many textbooks, like Cormen.

0

You will see a bunch of different indentation styles for Haskell. Most of them are very hard to maintain without an editor that is set up to indent exactly in whatever style.

The style you display is much simpler and less demanding of the editor, and I think you should stick with it. The only inconsistency I can see is that you put the first do on its own line while you put the other dos after the then/else.

Heed the other advice about how to think about code in Haskell, but stick to your indentation style.

Not the answer you're looking for? Browse other questions tagged or ask your own question.