A match expression has the general form:
match e with
pattern_1 -> block_1
pattern_2 -> block_2
…
pattern_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:
- The scrutinee is evaluated.
- The first pattern is evaluated and matched against the value of the scrutinee.
- 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_1
…
pattern_n -> block_n
-- equivalent to
e -> match e with
pattern_1 -> block_1
pattern_2 -> block_1
…
pattern_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
:
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.
List patterns
A list pattern matches a List t
for some type t
and has one of four forms:
head List.+: tail
matches a list with at least one element. The patternhead
is matched against the first element of the list andtail
is matched against the suffix of the list with the first element removed.init List.:+ last
matches a list with at least one element. The patterninit
is matched against the prefix of the list with the last element removed, andlast
is matched against the last element of the list.- A literal list pattern has the form
[p1, p2, … pn]
wherep1
throughpn
are patterns. The patternsp1
throughpn
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. part1 List.++ part2
matches a list which composed of the concatenation ofpart1
andpart2
. At least one ofpart1
orpart2
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 isstart List.++ [x, y]
, but justa ++ 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
:
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):
{C p1 p2 … pn -> k}
whereC
is the name of an ability constructor in scope, andp1
throughpn
are patterns such thatn
is the arity ofC
. Note thatn
may be zero. This pattern matches if the scrutinee reduces to a fully applied invocation of the ability constructorC
and the patternsp1
throughpn
match the arguments to the constructor. The scrutinee must be of typeRequest A T
for some ability{A}
and typeT
. The variablek
will be bound to the continuation of the program. If the scrutinee has typeRequest A T
andC
has typeX ->{A} Y
, thenk
has typeY -> {A} T
.{p}
wherep
is a pattern. This matches the case where the computation is pure (the value of typeRequest A T
calls none of the constructors of the ability{A}
). A pattern match on aRequest
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