Skip to Content
i documentation
The language standardChapters

7. Patterns

7.1 Grammar

Pattern   = Part { "=" Part } .
Part      = Atom | StringPat .
StringPat = string_lit "<>" Binder [ "<>" string_lit ] | Binder "<>" string_lit .
Binder    = name | discard .
Atom      = name | discard | ( name | discard ) ":" Annot
          | [ "-" ] int_lit | [ "-." ] float_lit | string_lit
          | Name [ DataPat ]
          | DataPat
          | "[" [ ListPats ] "]"
          | BitArrayPattern .
DataPat   = "(" [ DPItem { "," DPItem } [ "," ] ] ")" .
DPItem    = name "=" Pattern | Pattern | Rest .
PItem     = name "=" Choices | Choices .
Choices   = Pattern { "|" Pattern } .
ListPats  = Rest | Pattern { "," Pattern } [ "," [ Rest ] ] .
Rest      = ".." [ name | discard ] .
  • A Name in a pattern must be a constructor (an UpName, or m.C). Its data is matched by a data pattern C(…), with every key of its data type or .. at the end. C alone matches a labelled value whose data is (); C() is a compile error. A lowercase name followed by ( is not a pattern.
  • A data pattern ( … ) matches data of its shape whose parts match its items. A positional item matches the next position, and label = p matches the part under label. A data pattern lists every key of its shape, or ends with .., which leaves out the other keys, or ..r, which binds them as data: the remaining positions numbered again from 0 in order, and the remaining labels. .. requires exactly one known shape for the matched data, from a type or from the constructor. All possible incoming data members must have that same set of keys and number of positions. Knowing several different shapes is insufficient, even when each contains every explicit key of the pattern; a rest pattern is not expanded over those shapes. A positional item may not follow a labelled item.
  • A pattern is a conjunction p1 = … = pk: it matches what every part matches, and each part binds its names. Directly inside a data pattern or a parameter group, an item whose first two tokens are a name and = is a labelled item, never a conjunction. So to name a whole positional item, the name is written last: ((a, b) = args). (size = whole = (w, h)) is the label size, whose part matches both whole and (w, h). A positional item that is a conjunction of names starts with a discard: (_ = x = y).
  • A labelled part of a constructor’s data is matched only by its label: with User = (name = String, age = Int), User(name = n, age = a) and User(age = a, ..) are correct, and User(name, age) is a shape error. There is no shorthand for name = name.
  • Alternatives p1 | p2 are allowed only in the items of a parameter group (§6.11). Every alternative binds the same names, and a name gets the union of its types. | is looser than =.
  • A type after : uses Annot (§3), in parameter items, chain branches, binding patterns and data-pattern items alike. Thus (x : Int | y) has two alternatives, the type test x : Int and the binder y; it is rejected because the alternatives bind different names. (x : Int | x) binds the same name in both alternatives, and (True | _ : Int) keeps its meaning. Union annotations are (x : { Int | String }), v : { A | B } = e, and C(value = x : { Int | String }). This does not allow alternatives in places where they are otherwise forbidden.

x : T is one thing in every place where it may be written (in a bit array segment, : begins a format instead, §8.1). It matches the values of the type T, and x gets the type T. In a binding, and in a parameter item that is fitted (§4.8) and has no alternatives, every value that comes there is in T (§4.8 rules 4 and 10), so x : Int = e and (x : Int) { … } are type annotations. In a group typed by §4.8 rule 11, and in alternatives, x : T tests the value. At run time, a member of T is tested by its type constructor; a member of Int or String whose value set is not all the values is also tested by equality (§11.4) with the values in it. So x : { "a" | "b" } takes the strings "a" and "b", and x : String - "a" takes every other string, but no Int. So in a test, T may not have a type variable as a member. Opaque types cannot be told apart at run time, so for a type test they all count as one type constructor, as all function types do.

7.2 What patterns match

PatternMatchesNames
xAny valuex
A discardAny valueNone
A literal, or - or -. and a literalAn equal value (§11.4)None
C, C(…)A value made by C whose data matches the data patternThose of the items
A data patternData of its shape, or of a shape with more keys when it ends with .., whose parts match the itemsThose of the items, and the rest
[p1, …, pn]A list of exactly n parts that matchThose of the parts
[p1, …, pn, ..r]A list of n or more parts; r is the restThose of the parts, and r
"lit" <> rA string that starts with "lit"; r is the restr
r <> "lit"A string that ends with "lit"; r is the startr
"a" <> r <> "b"A string with that prefix and suffix, without overlap; r is the middler
p1 = … = pkWhat every part matchesThose of every part
x : TA value whose type constructor is that of a member of T; for a member of Int or String, the value must also be in the value set of the memberx
<< … >>See §8
  • A string pattern has exactly one binder (or discard), whose inferred and known type is String (§4.8). Its literals have no interpolation. a <> "x" <> b is a compile error. Prefix and suffix must not overlap: "ab" <> r <> "ba" does not match "aba".
  • A pattern may not bind one name two times.

7.3 Patterns that may fail

A pattern can not fail for a type T when it matches every value of T. Other patterns may fail. A catch-all can not fail for any type, and x : U can not fail for a type T with T <: U, so id = <A>(x : A) -> A { x } needs no Assert. A value of a type variable may be any value, so a pattern that tests more than its type, such as a literal or a constructor, may fail for it.

A literal pattern can not fail for its literal type, so "a" = x with x : "a" needs no Assert. Where a pattern is fitted (§4.8), a literal has the whole Int or String, so "a" = s with s : String is correct and may fail: it reports to Assert. A constructor pattern fitted to a union, as Ok(v) = r with r : { Ok(Int) | Error(String) }, is correct and may fail. Whether a pattern may fail is decided with every value that may come, also when the value is a direct value of the same group (§6.10), whose values are known only when the whole group is checked: in pair = ("b", () { ("a", _) = pair 1 }), the binding may fail, and the function reports to Assert. The same holds for a match whose chain has one branch and no guard.

7.4 Covering every value

Empty domains are determined by §4.1. They are covered vacuously, and no branch is useful for an empty domain.

The branches of a shape group whose parameter has the type A cover A when, for every value of A, some branch without a guard has a parameter group that matches it. A branch with a guard is not counted. A branch is never used when no value in the applicable parameter domain can reach it: every value in that domain that its pattern matches is matched by an earlier branch without a guard. Values outside the domain do not make a branch useful. In an inline match, use only the one-position shape group and the possible incoming subject values; branches of other shapes are never used (§6.12).

A literal pattern matches its own value, and string prefix/suffix patterns match the strings with their fixed ends. So literal patterns cover an Int or String member whose value set is finite, when every value in it is matched: with m : { "GET" | "POST" }, m |> ("GET") { 1 } | ("POST") { 2 } is covered. Literal patterns alone never cover a member with all its values, or with all but a finite set; a catch-all, a type test such as x : String - "a", or, for String, the patterns "" <> r and r <> "", which match every string, must take the other values. A literal in an item of a list pattern, or at a key of a constructor whose type parameter occurs more than once in its data (Twin(A) = (A, A)), has the whole Int or String (§4.8), so such literals cover a finite set only when that type is written or comes from the subject: (xs : List("a" | "b")) { xs |> ([]) { 0 } | (["a", ..]) { 1 } | (["b", ..]) { 2 } } is covered, and the same chain with (xs) is not. Literal patterns of Float and BitArray never cover their type. A union is covered when every member is covered. Data of one shape is one type constructor.

When the values that are not covered are only values of Int or String that no literal pattern takes, the type of the group already says so (§4.8 rule 11: (n : Int) { n |> (0) { "zero" } | (1) { "one" } } needs Int <: 0 | 1). In a match whose subject already has such a type, that is reported as this error alone, with a value that no branch matches, such as 2. A chain that is not the right side of a match gets these values later, through its parameter, so there it is a type error where the values come and also this error (§4.8 rule 11). When a value that is not covered is at a list item place, or at such a key, where the branches have only literals, the message also says to write that type, such as List("a" | "b") or Twin("a" | "b").

Only a group with more than one branch or with a guard must cover the type of its parameter (§6.12). A group with one branch and no guard is not checked for coverage: when its pattern may fail, it reports to Assert.

7.5 Known constructors

Successful matching gives a branch-local view of the matched value. A constructor pattern C(…) restricts that view to the incoming member made by C, with the type arguments established by its pattern. A conjunction that names the whole value gives its name this view. When the subject of a match is a name v, that name has the view inside the matching branch, regardless of the number of branches.

The view supplies both the inferred type used for the key bound (§4.8 rule 13) and the known constructor fact used for key visibility (§6.7). It does not constrain or replace the subject’s original type scheme outside the branch.

A = (value = Int)
B = (other = String)
f = (v : { A | B }) { v |> (A(value = _)) { v.value } | (B(other = _)) { 0 } }
main = () { f(A(value = 7)) }

Here v.value is checked with the A view. The parameter of f still admits both A and B.