Pattern matching
case is the elimination form for sum types and lists; a single-constructor
type may also be destructured where a parameter binds it (see
patterns in argument position). For the
branching if form see functions.
case … of
type Role = Web | Db
glyphsFor : Role -> List GlyphglyphsFor role = case role of Web -> [ aptPackage { name = "nginx" } ] Db -> [ aptPackage { name = "postgresql" } ]An arm is pattern -> expr. The arms of a case are laid out by the offside
rule — each arm on its own line, indented past of. A binder in a pattern is
in scope in that arm’s body.
Pattern forms
| Pattern | Matches | Example |
|---|---|---|
| variable | anything; binds it | n |
wildcard _ | anything; binds nothing | _ |
| Int literal | an equal integer | 0, -1 |
| Char literal | an equal Char | 'a' |
| String literal | an equal String | "nginx" |
| constructor | a value of that constructor | Just x, Web |
| empty list | [] | [] |
| cons | a non-empty list | (x :: xs) |
| list literal | a list of fixed length | [a, b] |
| tuple | a tuple, element-wise | (a, b), (a, b, c) |
| unit | () | () |
An integer literal pattern is typed number, not hard Int, so it may match a
Float scrutinee just as an integer literal expression may; a leading - is
part of the pattern (-1).
A Float literal pattern is a compile error: IEEE-754 equality is unreliable,
so — like Elm — Emet refuses to match on it and steers you to the comparison
operators (<, >, <=, >=) in an if instead. See
functions for if.
Constructor patterns
Upper p1 p2 … matches a constructor applied to sub-patterns. Constructors of
built-in sums (Just / Nothing, True / False, LT / EQ / GT) and of
user type declarations both work:
unwrap : Maybe Int -> Intunwrap m = case m of Just n -> n Nothing -> 0Constructors imported from another module with Type(..) match too, and the
exhaustiveness checker sees the imported type’s full constructor set. See
modules.
A built glyph or filesystem entry is matchable: AptPackage,
SystemdService, Filesystem, LineInFile, and the entry tags File,
Directory, Symlink are match-only PascalCase patterns. See
the four glyphs.
List patterns
[] matches the empty list; (x :: xs) matches a non-empty list, binding its
head and tail. A [a, b, c] literal matches a list of exactly that length and
desugars to nested :: ending in [].
firstOr : Int -> List Int -> IntfirstOr fallback xs = case xs of [] -> fallback (x :: _) -> xList is checked as a two-constructor sum ([] and ::), so a case on a
list must cover both.
Tuple patterns
(a, b) and (a, b, c) match a tuple element-wise; () matches unit. Nested
patterns compose, so (Just a, (b, c)) destructures a pair of a Maybe and a
tuple in one arm.
A tuple has a single shape, so a tuple case needs no catch-all when its
element patterns are exhaustive — it is non-exhaustive only when some element
column is.
describe : (Bool, Bool) -> Stringdescribe pair = case pair of (True, b) -> "first" (False, True) -> "second" (False, False) -> "neither"Exhaustiveness and redundancy
A non-exhaustive case — one that fails to cover some value — is a compile
error, reported with the missing constructors. A redundant arm — one that can
never match because earlier arms already cover it — is also a compile error.
Both are checked at compile time, so no arm falls through at runtime.
Patterns in argument position
A parameter of a declaration, a let binding, or a lambda may be a constructor
pattern, so a value destructures where it binds:
type Config = Config { domain : String, port : Int }
domainOf : Config -> StringdomainOf (Config spec) = spec.domain
withPort : Int -> Config -> ConfigwithPort p (Config spec) = Config { spec | port = p }labels : List Config -> List Stringlabels = List.map (\(Config spec) -> spec.domain)A parameter is a binder or a parenthesized constructor application, and
nothing else. Every other pattern form on this page — tuple, unit, list, literal,
and a constructor nested inside another — is a parse error in argument position,
and a nullary constructor still needs its parentheses (f (Unit) = …, not
f Unit = …).
The constructor must also be the only constructor of its type, which is what
the narrow grammar exists to guarantee. A parameter has no sibling arms to cover
the values it misses, so a pattern that can fail there would be a partial
function — the thing exhaustiveness checking exists to prevent, reached by
writing a binding instead of a branch. Take the whole value as a parameter and
branch on it with case … of, which is checked:
nameFor : Maybe String -> StringnameFor m = case m of Just name -> name Nothing -> "anonymous"An excluded form reports a general parse error at the parameter — f (a, b) = …
says found 'a' expected an expression — rather than naming argument position as
the thing that does not support it.
See also
- Values and types — literals, types, user sums.
- Functions — declarations,
let,if. - Modules —
module,import, the search path. - Prelude — the standard-library functions.