Match expressions and pattern matching

A match expression has the general form:

match e with
  pattern_1 -> block_1
  pattern_2 -> block_2pattern_n -> block_n

Where e is an expression, called the scrutinee of the match expression, and each case has a pattern to match against the value of the scrutinee and a block to evaluate in case it matches.

The evaluation semantics of match expressions are as follows:

  1. The scrutinee is evaluated.
  2. The first pattern is evaluated and matched against the value of the scrutinee.
  3. If the pattern matches, any variables in the pattern are substituted into the block to the right of its -> (called the match body) and the block is evaluated. If the pattern doesn’t match then the next pattern is tried and so on.

It's possible for Unison to actually evaluate cases in a different order, but such evaluation should always have the same observable behavior as trying the patterns in sequence.

It is an error if none of the patterns match. In this version of Unison, the error occurs at runtime. In a future version, this should be a compile-time error.

Unison provides syntactic sugar for match expressions in which the scrutinee is the sole argument of a lambda expression:

cases
  pattern_1 -> block_1
  pattern_2 -> block_1pattern_n -> block_n

-- equivalent to
e -> match e with
  pattern_1 -> block_1
  pattern_2 -> block_1pattern_n -> block_n

A pattern has one of the following forms:

Blank patterns

A blank pattern has the form _. It matches any expression without creating a variable binding.

For example:

_ = 42 "Always matches"
"Always matches"

Literal patterns

A literal pattern is a literal Boolean, Nat, Int, Char, or Text. A literal pattern matches if the scrutinee has that exact value.

For example:

match 2 Nat.+ 2 with 4 -> "Matches" _ -> "Doesn't match"
"Matches"

Variable patterns

A variable pattern is a regular identifier and matches any expression. The expression that it matches will be bound to that identifier as a variable in the match body.

For example:

match 1 + 1 with
  x -> x + 1

As-patterns

An as-pattern has the form v@p where v is a regular identifier and p is a pattern. This pattern matches if p matches, and the variable v will be bound in the body to the value matching p.

For example, this expression evaluates to 3:

matchExpression : Nat
matchExpression = match 1 Nat.+ 1 with
  x@4 -> x Nat.* 2
  y@2 -> y Nat.+ 1
  _   -> 22

Constructor patterns

A constructor pattern has the form C p1 p2 … pn where C is the name of a data constructor in scope, and p1 through pn are patterns such that n is the arity of C. Note that n may be zero. This pattern matches if the scrutinee reduces to a fully applied invocation of the data constructor C and the patterns p1 through pn match the arguments to the constructor.

For example, this expression uses Some and Optional.None, the constructors of the Optional type, to return the 3rd element of the list xs if present or 0 if there was no 3rd element.

xs = [0, 2, 3, 4, 5] match List.at 3 xs with Optional.None -> 0 Some x -> x
4

List patterns

A list pattern matches a List t for some type t and has one of four forms:

  1. head List.+: tail matches a list with at least one element. The pattern head is matched against the first element of the list and tail is matched against the suffix of the list with the first element removed.
  2. init List.:+ last matches a list with at least one element. The pattern init is matched against the prefix of the list with the last element removed, and last is matched against the last element of the list.
  3. A literal list pattern has the form [p1, p2, … pn] where p1 through pn are patterns. The patterns p1 through pn are matched against the elements of the list. This pattern only matches if the length of the scrutinee is the same as the number of elements in the pattern. The pattern [] matches the empty list.
  4. part1 List.++ part2 matches a list which composed of the concatenation of part1 and part2. At least one of part1 or part2 must be a pattern with a known list length, otherwise it's unclear where the list is being split. For instance, [x, y] List.++ rest is okay as is start List.++ [x, y], but just a ++ b is not allowed.

Examples:

first : [a] -> Optional a
first = cases
  h +: _ -> Some h
  []     -> None
last : [a] -> Optional a
last = cases
  _ :+ l -> Some l
  []     -> None
exactlyOne : [a] -> Boolean
exactlyOne = cases
  [_] -> true
  _   -> false
lastTwo : [a] -> Optional (a, a)
lastTwo = cases
  start ++ [a, a2] -> Some (a, a2)
  _                -> None
firstTwo : [a] -> Optional (a, a)
firstTwo = cases
  [a, a2] ++ rest -> Some (a, a2)
  _               -> None

Tuple patterns

A tuple pattern has the form (p1, p2, … pn) where p1 through pn are patterns. The pattern matches if the scrutinee is a tuple of the same arity as the pattern and p1 through pn match against the elements of the tuple. The pattern (p) is the same as the pattern p, and the pattern () matches the literal value () of the trivial type {()} (both pronounced “unit”).

For example, this expression evaluates to 4:

(a, _, c) = (1, 2, 3) a Nat.+ c
4

Ability patterns (or Request patterns)

An ability pattern only appears in an ability handler and has one of two forms (see Abilities and ability handlers for details):

  1. {C p1 p2 … pn -> k} where C is the name of an ability constructor in scope, and p1 through pn are patterns such that n is the arity of C. Note that n may be zero. This pattern matches if the scrutinee reduces to a fully applied invocation of the ability constructor C and the patterns p1 through pn match the arguments to the constructor. The scrutinee must be of type Request A T for some ability {A} and type T. The variable k will be bound to the continuation of the program. If the scrutinee has type Request A T and C has type X ->{A} Y, then k has type Y -> {A} T.
  2. {p} where p is a pattern. This matches the case where the computation is pure (the value of type Request A T calls none of the constructors of the ability {A}). A pattern match on a Request is not complete unless this case is handled.

See the section on abilities and ability handlers for examples of ability patterns.

Guard patterns

A guard pattern has the form p | g where p is a pattern and g is a Boolean expression that may reference any variables bound in p. The pattern matches if p matches and g evaluates to true.

For example, the following expression evaluates to 6:

match 1 + 2 with
  x
    | x == 4     -> 0
    | x + 1 == 4 -> 6
  _              -> 42