Skip to content

33-模式匹配

模式匹配主要是通过关键字 match匹配到合适的值,再去执行对应的内容。语法层面可以理解为很多编程语言中的switch-case

模式匹配的分类

基于要匹配的数据类型不同,可以分成以下几种分类:

  1. 常量模式
  2. 通配符模式
  3. 绑定模式
  4. Tuple 模式
  5. 类型模式
  6. enum 模式
  7. 模式的嵌套组合

常量模式

常量模式可以是整数字面量、浮点数字面量、字符字面量、布尔字面量、字符串字面量(不支持字符串插值)、Unit 字面量。

数字匹配

| 表示可以连接多个模式

javascript
main() {
    let score = 90
    let level = match (score) {
        case 0 | 10 | 20 | 30 | 40 | 50 => "D"
        case 60 => "C"
        case 70 | 80 => "B"
        case 90 | 100 => "A" // Matched.
        case _ => "Not a valid score"
    }
    println(level)
}

字符匹配

在模式匹配的目标是静态类型为 Rune 的值时,Rune 字面量和单字符字符串字面量都可用于表示 Rune 类型字面量的常量pattern

javascript
func translate(n: Rune) {
    match (n) {
        case "A" => 1
        case "B" => 2
        case "C" => 3
        case _ => -1
    }
}

main() {
    println(translate(r"C"))
}

Byte匹配

一个表示 ASCII 字符的字符串字面量可用于表示 Byte 类型字面量的常量 pattern

javascript
func translate(n: Byte) {
    match (n) {
        case "1" => 1
        case "2" => 2
        case "3" => 3
        case _ => -1   // 通配符模式 
    }
}

main() {
    println(translate(51)) // UInt32(r'3') == 51
}

通配符模式

_ 表示通配符模式,当上述所有的条件都不匹配时,便会匹配到通配符 _

javascript
func translate(n: Byte) {
    match (n) {
        case "1" => 1
        case "2" => 2
        case "3" => 3
        case _ => -1
    }
}

main() {
    println(translate(51)) // UInt32(r'3') == 51
}

绑定模式

绑定模式类似通配符模式,区别是可以把当前符合条件的变量绑定到某个变量上。

绑定模式使用 id 表示,id 是一个合法的标识符。

id不是具体的名称,理解是一占位符即可。实际上可以通过各种变量名来表示 id

也可以使用多个,不过只能是第一个会匹配到。

javascript
main() {
    let x = -10
    let y = match (x) {
        case 0 => "zero"
        case n => "x is not zero and x = ${n}" // Matched.   n  表示 id    
    }
    println(y)
}

需要注意的是,绑定模式不能和 | 连接多个模式共用,也不可嵌套出现在其它模式中

javascript
main() {
    let opt = Some(0)
    match (opt) {
        case x | x => {} // Error, variable cannot be introduced in patterns connected by '|'
        case Some(x) | Some(x) => {} // Error, variable cannot be introduced in patterns connected by '|'
        case x: Int64 | x: String => {} // Error, variable cannot be introduced in patterns connected by '|'
    }
}

Tuple 模式

Tuple 模式用于 tuple 值的匹配

javascript
main() {
    let tv = ("Alice", 24)
    let s = match (tv) {
        case ("Bob", age) => "Bob is ${age} years old"
        case ("Alice", age) => "Alice is ${age} years old" // Matched, "Alice" is a constant pattern, and 'age' is a variable pattern.
        case (name, 100) => "${name} is 100 years old"
        case (_, _) => "someone"
    }
    println(s)
}

类型模式

类型模式用于判断一个值的运行时类型是否是某个类型的子类型。类型模式有两种形式:_: Type(嵌套一个通配符模式 _)和 id: Type(嵌套一个绑定模式 id),它们的差别是后者会发生变量绑定,而前者并不会

javascript
open class Base {
    var a: Int64
    public init() {
        a = 10
    }
}

class Derived <: Base {
    public init() {
        a = 20
    }
}

main() {
    var d = Derived()
    var r = match (d) {
        case b: Base => b.a // Matched.
        case _ => 0
    }
    println("r = ${r}")
}

enum 模式

enum 模式用于匹配 enum 类型的实例

javascript
enum TimeUnit {
    | Year(UInt64)
    | Month(UInt64)
}

main() {
    let x = Year(2)
    let s = match (x) {
        case Year(n) => "x has ${n * 12} months" // Matched.
        case TimeUnit.Month(n) => "x has ${n} months"
    }
    println(s)
}

使用 | 连接多个 enum 模式:

javascript
enum TimeUnit {
    | Year(UInt64)
    | Month(UInt64)
}

main() {
    let x = Year(2)
    let s = match (x) {
        case Year(0) | Year(1) | Month(_) => "Ok" // Ok
        case Year(2) | Month(m) => "invalid" // Error, Variable cannot be introduced in patterns connected by '|'
        case Year(n: UInt64) | Month(n: UInt64) => "invalid" // Error, Variable cannot be introduced in patterns connected by '|'
    }
    println(s)
}

使用 match 表达式匹配 enum 值时,要求 case 之后的模式要覆盖待匹配 enum 类型中的所有构造器,如果未做到完全覆盖,

编译器将报错:

javascript
enum RGBColor {
    | Red | Green | Blue
}

main() {
    let c = Green
    let cs = match (c) { // Error, Not all constructors of RGBColor are covered.
        case Red => "Red"
        case Green => "Green"
    }
    println(cs)
}

模式的嵌套组合

Tuple 模式和 enum 模式可以嵌套任意模式。

javascript
enum TimeUnit {
    | Year(UInt64)
    | Month(UInt64)
}

enum Command {
    | SetTimeUnit(TimeUnit)
    | GetTimeUnit
    | Quit
}

main() {
    let command = SetTimeUnit(Year(2022))
    match (command) {
        case SetTimeUnit(Year(year)) => println("Set year ${year}")
        case SetTimeUnit(Month(month)) => println("Set month ${month}")
        case _ => ()
    }
}

Released under the MIT License.