case expressions
| Match on shape | |
| case is an expression | |
| The wildcard | |
| Nested cases |
Match on shape
case inspects a value and runs the first alternative whose pattern fits. It is the tool for deciding on the structure of data rather than on a boolean condition:
classify :: Maybe Int -> String
classify v = case v of
Nothing -> "empty"
Just 0 -> "zero"
Just n -> "number " ++ show n
Patterns are tried top to bottom, so the literal zero must come before the variable pattern that would otherwise catch it. A variable pattern matches everything.
case is an expression
Like if, a case produces a value, so it can be an argument or the right side of a binding:
label = "value: " ++ case lookup 1 table of
Nothing -> "missing"
Just v -> show v
Indentation matters. The alternatives line up under each other, and the whole case belongs to the expression that started it.
The wildcard
An underscore matches anything and binds nothing. Use it when the value is irrelevant, and never use a fresh variable name for the same purpose, because a named variable silences the unused binding warning:
ok :: Either String Int -> Bool
ok r = case r of
Right _ -> True
Left _ -> False
A trailing _ -> branch turns the match into a partial function. It is fine for a small enumeration and dangerous for a growing sum type, where a new constructor should break the build rather than fall into the default.
Nested cases
Two cases in a row are a smell. Usually the pattern can carry both levels at once, or the inner decision belongs in its own function:
-- flat: both shapes in one pattern
pair :: Maybe (Maybe Int) -> Int
pair (Just (Just n)) = n
pair _ = 0
Function equations with several patterns are the same machinery as a
case, only with a shorter syntax. The next lesson compares the two
forms directly.
Next:
Pattern matching
Article author: Arthur Isaev