Haskell/Control structures
Haskell offers several ways of expressing a choice between different values. We explored them a bit in the Next steps section of the Haskell Basics chapter. 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>
Here, condition is an expression which evaluates to a boolean. 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 (which is converted to a value) – and not a statement (which is executed), as usual for imperative languages. A very important consequence of this fact is that in Haskell the else is required. Since if is an expression, it must evaluate to a result, and the else ensures this. For the same reason, the usual indentation is different from imperative languages. If you need to split 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."
[edit] case Expressions
One interesting way of thinking about case expressions is seeing them as a generalization of if expressions. We could even write a clone of if as a case:
alternativeIf :: Bool -> a -> a -> a alternativeIf cond ifTrue ifFalse = case cond of True -> ifTrue False -> ifFalse
First, this checks cond for a pattern match against True. If there is a match, the whole expression will evaluate to ifTrue, otherwise it will evaluate to ifFalse (since a Bool can only be True or False there is no need for a default case). case is more general than if because the pattern matching in which case selection is based allows it to work with expressions which evaluate to values of any type.[1] In fact, the left hand side of any case branch is just a pattern, so it can also be used for binding:
describeString :: String -> String describeString str = case str of (x:xs) -> "The first character is: " ++ [x] ++ "; the rest of the string is: " ++ xs "" -> "This is an empty string."
This expression tells you whether str is an empty string or something else. Of course, you could just do this with an if-statement (with a condition of 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
Remember you can use multiple equations as an alternative to case expressions. The describeString function above could be 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.
One handy thing about case expressions, and indeed about Haskell control structures in general, is that since they evaluate to values they can go inside other expressions just like an ordinary expression would. For example, this case expression evaluates to a string which is then concatenated with two other strings, all inside a single expression:
data Colour = Black | White | RGB Int Int Int describeColour :: Colour -> String describeColour c = "This colour is " ++ case c of Black -> "black" White -> "white" RGB 0 0 0 -> "black" RGB 255 255 255 -> "white" _ -> "freaky, man, sort of in between" ++ ", yeah?"
Writing this function in an equivalent way using the multiple equations style would need a named function inside something like a let block.
[edit] Guards
As shown, if we have a top-level case expression, we can just give multiple equations for the function instead, which is often neater. Is there an analogue for if expressions? It turns out there is. We use some additional syntax known as "guards", previously introduced in the Haskell Basics section on Truth values. 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. Unlike the else in if statements, otherwise is not mandatory. Still, if no guards match an error will be produced at runtime, so it's always a good idea to provide the 'otherwise' guard, even if it is just to handle the "But this can't happen!" case (which normally does happen anyway...).
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] Notes
- ↑ Again, this is quite different from what happens in most imperative languages, in which switch/case statements are restricted to equality tests, often only on integral primitive types.
This page may need to be