if, guards, and pattern guards
| Guards | |
| otherwise is a value | |
| Pattern guards | |
| When to use which |
Guards
A guard is a condition written between the arguments and the equals sign. GHC tries the equations from top to bottom and uses the first one whose guard is true:
grade :: Int -> String
grade n
| n >= 90 = "A"
| n >= 75 = "B"
| n >= 60 = "C"
| otherwise = "F"
The same logic as nested if expressions occupies half the lines and reads as a table. Order is the logic: the first matching guard wins, so the strongest condition goes first.
otherwise is a value
otherwise is not syntax. It is an ordinary name bound to True in the standard library, which is why it works as a catch-all:
otherwise :: Bool
otherwise = True
A function whose guards all fail raises a pattern match failure at runtime. The compiler cannot prove that your conditions cover every input, so the catch-all guard is your promise that they do.
Pattern guards
A guard may also match a pattern. When the pattern fails, the guard is skipped, which makes lookups read without a case:
describe :: Maybe Int -> String
describe m
| Just n <- m, n > 0 = "positive " ++ show n
| Just _ <- m = "not positive"
| otherwise = "nothing"
Commas separate several conditions, and all of them must hold. A failed pattern short-circuits the rest, so n is safe to use in the following conditions.
When to use which
Use if for a single binary choice inside a larger expression. Use guards for a table of conditions on the arguments. Use case when the shape of a value decides the branch, and lesson 11 covers that next.
abs2 n = if n < 0 then negate n else n
sign n
| n < 0 = -1
| n > 0 = 1
| otherwise = 0
Mixing the three styles in one function is where readability dies.
Pick the one that matches the shape of the decision and stay with it
to the end of the function.
Next:
case expressions
Article author: Arthur Isaev