跳至內容

Haskell/控制結構

維基教科書,自由的教學讀本

Haskell提供了幾種方式來表達在不同的值間選擇。本節描述了所有的方式並解釋了他們的用途:

if 表達式

[編輯]

你已經見過這些表達式了。完整的語法是:

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

這裏,條件是一個求值後得到一個布朗型的表達式。如果<條件>True的,那麼<true-value>被返回,否則<false-value>被返回。注意Haskell中if是一個(被轉化為一個值的)表達式──並非如通常的命令式語言那樣是一個(被執行的)語句。這個事實的一個非常重要的後果則是Haskell中else是"必須的"。由於if是一個表達式,它必須求值得到一個結果,else保證了這一點。由於同一原因,通常的縮進同命令式語言有所不同。如果你需要把一個if表達式分為數行,那你應採用下面的縮進方式之一:

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

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

下面是一個簡單的例子:

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

case表達式

[編輯]

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.

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.

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". 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.

Notes

[編輯]
  1. 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.



控制結構
Haskell基礎

起步  >> 變量和函數  >> 列表和元組  >> 更進一步  >> 類型基礎  >> 簡單的輸入輸出  >> 類型聲明


Haskell

Haskell基礎 >> 初級Haskell >> Haskell進階 >> Monads
高級Haskell >> 類型的樂趣 >> 理論提升 >> Haskell性能


庫參考 >> 普通實務 >> 特殊任務