Haskell/Control structures

From Wikibooks, the open-content textbooks collection

Jump to: navigation, search



Control structures (Solutions)

Contents

Elementary Haskell

Recursion
List processing
More about lists
Pattern matching
Control structures
More on functions
Higher order functions Development stage: 50% (as of unknown)(unknown)

Haskell offers several ways of expressing a choice between different values. This section will describe them all and explain what they are for:

[edit] if Expressions

You have already seen these. The full syntax is:

if <condition> then <true-value> else <false-value>
The else is required!

If the <condition> is True then the <true-value> is returned, otherwise the <false-value> is returned. Note that in Haskell if is an expression (returning a value) rather than a statement (to be executed). Because of this the usual indentation is different from imperative languages. If you need to break an if expression across multiple lines then you should indent it like one of these:

if <condition>
   then <true-value>
   else <false-value>
if <condition>
   then
      <true-value>
   else
      <false-value>

Here is a simple example:

message42 :: Integer -> String
message42 n =
   if n == 42
      then "The Answer is forty two."
      else "The Answer is not forty two."

Unlike many other languages, in Haskell the else is required. Since if is an expression, it must return a result, and the else ensures this.

[edit] case Expressions

case expressions are a generalization of if expressions. As an example, let's clone if as a case:

case <condition> of
     True  -> <true-value>
     False -> <false-value>
     _     -> error "Neither True nor False? File Not Found!"

First, this checks <condition> for a pattern match against True. If they match, the whole expression will evaluate to <true-value>, otherwise it will continue down the list. You can use _ as the pattern wildcard. In fact, the left hand side of any case branch is just a pattern, so it can also be used for binding:

case str of
   (x:xs) -> "The first character is " ++ [x] ++ "; the rest of the string is " ++ xs
   ""     -> "This is the empty string."

This expression tells you whether str is the empty string or something else. Of course, you could just do this with an if-statement (with a condition of null str), but using a case binds variables to the head and tail of our list, which is convenient in this instance.

[edit] Equations and Case Expressions

You can use multiple equations as an alternative to case expressions. The case expression above could be named describeString and written like this:

describeString :: String -> String
describeString (x:xs) = "The first character is " ++ [x] ++ "; the rest of the string is " ++ xs
describeString ""     = "This is the empty string."

Named functions and case expressions at the top level are completely interchangeable. In fact the function definition form shown here is just syntactic sugar for a case expression.

The handy thing about case expressions is that they can go inside other expressions, or be used in an anonymous function. TODO: this isn't really limited to case. For example, this case expression returns a string which is then concatenated with two other strings to create the result:

data Colour = Black | White | RGB Int Int Int

describeColour c = 
   "This colour is "
   ++ (case c of
          Black -> "black"
          White -> "white"
          RGB _ _ _ -> "freaky, man, sort of in between")
   ++ ", yeah?"

You can also put where clauses in a case expression, just as you can in functions:

describeColour c = 
   "This colour is "
   ++ (case c of
          Black -> "black"
          White -> "white"
          RGB red green blue -> "freaky, man, sort of " ++ show av
             where av = (red + green + blue) `div` 3
      )
   ++ ", yeah?"

[edit] Guards

As shown, if we have a top-level case expression, we can just give multiple equations for the function instead, which is normally neater. Is there an analogue for if expressions? It turns out there is.

We use some additonal syntax known as "guards". A guard is a boolean condition, like this:

describeLetter :: Char -> String
describeLetter c
   | c >= 'a' && c <= 'z' = "Lower case"
   | c >= 'A' && c <= 'Z' = "Upper case"
   | otherwise            = "Not a letter"

Note the lack of an = before the first |. Guards are evaluated in the order they appear. That is, if you have a set up similar to the following:

f (pattern1) | predicate1 = w
             | predicate2 = x
f (pattern2) | predicate3 = y
             | predicate4 = z

Then the input to f will be pattern-matched against pattern1. If it succeeds, then predicate1 will be evaluated. If this is true, then w is returned. If not, then predicate2 is evaluated. If this is true, then x is returned. Again, if not, then we jump out of this 'branch' of f and try to pattern match against pattern2, repeating the guards procedure with predicate3 and predicate4. If no guards match, an error will be produced at runtime, so it's always a good idea to leave an 'otherwise' guard in there to handle the "But this can't happen!" case.

The otherwise you saw above is actually just a normal value defined in the Standard Prelude as:

otherwise :: Bool
otherwise = True

This works because of the sequential evaluation described a couple of paragraphs back: if none of the guards previous to your 'otherwise' one are true, then your otherwise will definitely be true and so whatever is on the right-hand side gets returned. It's just nice for readability's sake.

[edit] 'where' and guards

One nicety about guards is that where clauses are common to all guards.

 doStuff x
   | x < 3 = report "less than three"
   | otherwise = report "normal"
  where
   report y = "the input is " ++ y


[edit] The difference between if and case

It's worth noting that there is a fundamental difference between if-expressions and case-expressions. if-expressions, and guards, only check to see if a boolean expression evaluated to True. case-expressions, and multiple equations for the same function, pattern match against the input. Make sure you understand this important distinction.