match Expression
Definition
In Cangjie, a match
expression may contain or not contain a value to be matched.
A match expression with a value is as follows:
main() {
let x = 0
match (x) {
case 1 => let r1 = "x = 1"
print(r1)
case 0 => let r2 = "x = 0" // Matched.
print(r2)
case _ => let r3 = "x != 1 and x != 0"
print(r3)
}
}
The match
expression starts with the keyword match
, followed by the value to be matched (for example, x
in the preceding example, which can be any expression), and then several case
branches defined in a pair of braces.
Each case
branch starts with the keyword case
, which is followed by one or multiple patterns of the same type connected by |
(1
, 0
, and _
in the preceding example are patterns. For details, see Pattern Overview). A pattern can be followed by an optional pattern guard
, indicating additional conditions that must be satisfied after the case
is successfully matched. Then, there is a =>
, followed by the operations to be executed if this case
branch matches. The operations can be a series of expressions, variables, and function definitions (the scope of a newly defined variable or function starts from the definition and ends before the next case
), for example, the variable definitions and print
function calls in the preceding example.
During execution, the match
expression sequentially matches the expression following match
with the patterns in each case
. Once a match is found (if there is a pattern guard
, the expression following where
must also evaluate to true
; if there are multiple patterns connected by |
, matching against any one of them counts as a successful match), the code following =>
is executed, and the execution of the match
expression exits (meaning it will not match against any subsequent cases
). If no match is found, it continues to match against the patterns in the following cases
until a match is successful (the match
expression guarantees that there will always be a matching case
).
In the above example, since the value of x
is 0
, it will match with the second case
branch (here, the constant pattern is used to check whether the values are the same. For details, see Constant Pattern), resulting in the output: x = 0
.
Compile and execute the preceding code. The output is as follows:
x = 0
The match
expression requires that all matches be exhaustive, which means that all possible values of the expression to be matched must be considered. If the match
expression is not exhaustive or the compiler cannot determine whether the expression is exhaustive, a compilation error is reported. In other words, the union set of the value ranges covered by all case
branches (including pattern guards) should contain all possible values of the expression to be matched. A common way to ensure that the match
expression is exhaustive is to use the wildcard pattern _
in the last case
branch, because _
can match any value.
The exhaustiveness of the match
expression ensures that there must be a case
branch that matches the value. In the following example, an error is reported during compilation because all cases
do not cover all possible values of x
:
func nonExhaustive(x: Int64) {
match (x) {
case 0 => print("x = 0")
case 1 => print("x = 1")
case 2 => print("x = 2")
}
}
After the patterns in the case
branches, a pattern guard
can be used to further evaluate the matched results. The pattern guard
is represented by where cond
. The expression cond
must be of the Bool
type.
In the following example (the enum
pattern is used. For details, see Enum Pattern), when the parameter values of the RGBColor
constructor are greater than or equal to 0
, their values are printed. When the parameter values are less than 0
, they are considered to be equal to 0
.
enum RGBColor {
| Red(Int16) | Green(Int16) | Blue(Int16)
}
main() {
let c = RGBColor.Green(-100)
let cs = match (c) {
case Red(r) where r < 0 => "Red = 0"
case Red(r) => "Red = ${r}"
case Green(g) where g < 0 => "Green = 0" // Matched.
case Green(g) => "Green = ${g}"
case Blue(b) where b < 0 => "Blue = 0"
case Blue(b) => "Blue = ${b}"
}
print(cs)
}
The result is as follows:
Green = 0
A match expression without a value is as follows:
main() {
let x = -1
match {
case x > 0 => print("x > 0")
case x < 0 => print("x < 0") // Matched.
case _ => print("x = 0")
}
}
In contrast to a match
expression with a value, there is no expression to match after the match
keyword, and the cases
do not contain patterns
but rather expressions of the Bool
type (such as x > 0
and x < 0
in the code above) or _
(which represents true
). Additionally, there are no pattern guards
in the cases
.
When a match
expression without a value is executed, the values of the expressions following case
are checked in sequence until a case
branch whose value is true
is found. If the value of the expression following a case
is true
, the code following the =>
in that case
is executed, and the execution of the match expression is exited (meaning it will not evaluate any subsequent cases
).
In the preceding example, since the value of x
is -1
, the value of the expression (x < 0
) in the second case
branch is true
. In this case, print("x < 0")
is executed.
Compile and execute the preceding code. The output is as follows:
x < 0
Types of the match Expression
For match
expressions (regardless of whether there is a value):
-
When there is a clear type requirement in the context, the type of the code block following
=>
in eachcase
branch must be a subtype of the type required by the context. -
When there is no explicit type requirement in the context, the type of the
match
expression is the minimum common parent type of the code blocks following=>
in eachcase
branch. -
When the value of the
match
expression is not used, its type isUnit
, and there is no requirement for the branches to have the minimum common parent type.
Here are examples to illustrate these points.
let x = 2
let s: String = match (x) {
case 0 => "x = 0"
case 1 => "x = 1"
case _ => "x != 0 and x != 1" // Matched.
}
In this example, the variable s
is explicitly annotated with the type String
, which is a case where the type information in the context is clear. Therefore, each case
's code block following =>
must have a type that is a subtype of String
. Clearly, the string literals after =>
in the above example satisfy this requirement.
Another example without context type information is as follows:
let x = 2
let s = match (x) {
case 0 => "x = 0"
case 1 => "x = 1"
case _ => "x != 0 and x != 1" // Matched.
}
In this example, the variable s
is not explicitly annotated with a type. However, since the type of the code blocks following =>
in each case
is String
, the type of the match
expression is also String
, which means the type of s
can be determined to be String
as well.