重学仓颉-6枚举与模式匹配完全指南

引言

在仓颉语言中,枚举(Enum)和模式匹配(Pattern Matching)是两个非常强大的特性,它们为代码提供了类型安全和表达力。枚举允许我们定义一组相关的值,而模式匹配则让我们能够优雅地处理这些值的不同情况。

1. 枚举类型(Enum)

1.1 枚举的基本概念

枚举类型是仓颉语言中定义一组相关值的强大工具。它类似于函数式编程语言中的代数数据类型(Algebraic Data Types),提供了类型安全和表达力。

1.2 枚举的定义

枚举使用 enum 关键字定义,基本语法如下:

cangjie 复制代码
enum EnumName {
    | Constructor1
    | Constructor2(ParamType)
    | Constructor3(ParamType1, ParamType2)
}

1.2.1 基本枚举示例

cangjie 复制代码
// 简单的颜色枚举
enum Color {
    | Red | Green | Blue
}

// 带参数的RGB颜色枚举
enum RGBColor {
    | Red
    | Green
    | Blue
    | Red(UInt8)
    | Green(UInt8)
    | Blue(UInt8)
}

1.2.2 非穷尽枚举(Non-exhaustive Enum)

仓颉支持非穷尽枚举,使用 ... 构造器:

cangjie 复制代码
enum T {
    | Red | Green | Blue | ...
}

重要说明:

  • ... 构造器最多只能有一个,且必须是最后一个
  • 非穷尽枚举在模式匹配时需要特殊处理
  • ... 构造器不能被直接匹配

1.2.3 递归枚举

枚举支持递归定义,这对于定义树形结构非常有用:

cangjie 复制代码
enum Expr {
    | Num(Int64)
    | Add(Expr, Expr)
    | Sub(Expr, Expr)
}

// 使用递归枚举构建表达式
let expr = Add(Num(5), Sub(Num(10), Num(3)))

1.2.4 枚举中的成员

枚举可以包含成员函数、操作符函数和属性:

cangjie 复制代码
enum RGBColor {
    | Red | Green | Blue

    public static func printType(): Unit {
        print("RGBColor")
    }

    public func getColorName(): String {
        match (this) {
            case Red => "红色"
            case Green => "绿色"
            case Blue => "蓝色"
        }
    }
}

注意事项:

  • 构造器、成员函数、成员属性之间不能重名
  • 枚举只能定义在源文件的顶层作用域

1.3 枚举的使用

1.3.1 创建枚举实例

cangjie 复制代码
enum RGBColor <: ToString {
    | Red | Green | Blue(UInt8)

    public func toString(): String {
        match (this) {
            case Red => "红色"
            case Green => "绿色"
            case Blue(v) => "蓝色 ${v}"
        }
    }
}

main() {
    // 方式1:使用类型名.构造器
    let r = RGBColor.Red

    // 方式2:直接使用构造器(当无歧义时)
    let g = Green
    let b = Blue(100)

    print("r = ${r}, g = ${g}, b = ${b}")
}

1.3.2 名称冲突处理

当枚举构造器与其他标识符冲突时,必须使用类型名:

cangjie 复制代码
let Red = 1

func Green(g: UInt8) {
    return g
}

enum RGBColor {
    | Red | Green(UInt8) | Blue(UInt8)
}

main() {
    let r1 = Red // 选择 'let Red'
    let r2 = RGBColor.Red // 正确:使用枚举类型名

    let g1 = Green(100) // 选择 'func Green'
    let g2 = RGBColor.Green(100) // 正确:使用枚举类型名

    let b = Blue(100) // 正确:可以唯一识别为枚举构造器
}

2. 模式匹配(Pattern Matching)

2.1 match 表达式概述

仓颉支持两种 match 表达式:

  1. 包含待匹配值的 match 表达式
  2. 不含待匹配值的 match 表达式

2.2 包含匹配值的 match 表达式

2.2.1 基本语法

cangjie 复制代码
match (expression) {
    case pattern1 => expression1
    case pattern2 => expression2
    case _ => defaultExpression
}

2.2.2 基本示例

cangjie 复制代码
main() {
    let x = 0
    let result = match (x) {
        case 1 => "x = 1"
        case 0 => "x = 0" // 匹配成功
        case _ => "x != 1 and x != 0"
    }
    print(result)
}

执行结果:

ini 复制代码
x = 0

2.2.3 穷尽性要求

match 表达式要求所有匹配必须是穷尽的,这意味着待匹配表达式的所有可能取值都应该被考虑到:

cangjie 复制代码
// 错误示例:非穷尽匹配
func nonExhaustive(x: Int64) {
    match (x) {
        // non-exhaustive patterns, _ is not coveredCangjie
        case 0 => print("x = 0")
        case 1 => print("x = 1")
        case 2 => print("x = 2")
        // 缺少其他情况的处理
    }
}

正确的穷尽匹配:

cangjie 复制代码
func exhaustive(x: Int64) {
    match (x) {
        case 0 => print("x = 0")
        case 1 => print("x = 1")
        case 2 => print("x = 2")
        case _ => print("x is other value")
    }
}

2.2.4 非穷尽枚举的匹配

对于非穷尽枚举,需要使用可匹配所有构造器的模式:

cangjie 复制代码
enum T {
    | Red | Green | Blue | ...
}

func foo(a: T) {
    match (a) {
        case Red => 0
        case Green => 1
        case Blue => 2
        case _ => -1 // 使用通配符模式
    }
}

func bar(a: T) {
    match (a) {
        case Red => 0
        case k => -1 // 使用绑定模式
    }
}

func baz(a: T) {
    match (a) {
        case Red => 0
        case k: T => -1 // 使用带类型的绑定模式
    }
}

2.2.5 模式守卫(Pattern Guard)

使用 where 关键字添加额外的匹配条件:

cangjie 复制代码
enum RGBColor {
    | Red(Int16) | Green(Int16) | Blue(Int16)
}

main() {
    let c = RGBColor.Green(-100)
    let result = match (c) {
        case Red(r) where r < 0 => "Red = 0"
        case Red(r) => "Red = ${r}"
        case Green(g) where g < 0 => "Green = 0" // 匹配成功
        case Green(g) => "Green = ${g}"
        case Blue(b) where b < 0 => "Blue = 0"
        case Blue(b) => "Blue = ${b}"
    }
    print(result)
}

执行结果:

ini 复制代码
Green = 0

2.3 不含匹配值的 match 表达式

这种 match 表达式直接判断布尔表达式:

cangjie 复制代码
main() {
    let x = -1
    let result = match {
        case x > 0 => "x > 0"
        case x < 0 => "x < 0" // 匹配成功
        case _ => "x = 0"
    }
    print(result)
}

执行结果:

复制代码
x < 0

2.4 match 表达式的类型

2.4.1 上下文类型明确时

cangjie 复制代码
let x = 2
let s: String = match (x) {
    case 0 => "x = 0"
    case 1 => "x = 1"
    case _ => "x != 0 and x != 1"
}

2.4.2 上下文类型不明确时

cangjie 复制代码
let x = 2
let s = match (x) {
    case 0 => "x = 0"
    case 1 => "x = 1"
    case _ => "x != 0 and x != 1"
}
// s 的类型自动推断为 String

3. 模式类型详解

3.1 常量模式(Constant Pattern)

常量模式可以是各种字面量:

cangjie 复制代码
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" // 匹配成功
        case _ => "Not a valid score"
    }
    println(level)
}

执行结果:

css 复制代码
A

3.1.1 特殊常量模式

Rune 类型匹配:

cangjie 复制代码
func translate(n: Rune) {
    match (n) {
        case "A" => 1
        case "B" => 2
        case "C" => 3
        case _ => -1
    }
}

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

Byte 类型匹配:

cangjie 复制代码
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
}

3.2 通配符模式(Wildcard Pattern)

使用 _ 表示,可以匹配任意值:

cangjie 复制代码
func wildcardExample(x: Int64) {
    match (x) {
        case _ => println("x is any value")
    }
}

3.3 绑定模式(Binding Pattern)

使用标识符绑定匹配的值:

cangjie 复制代码
main() {
    let x = -10
    let y = match (x) {
        case 0 => "zero"
        case n => "x is not zero and x = ${n}" // 匹配成功
    }
    println(y)
}

执行结果:

ini 复制代码
x is not zero and x = -10

重要限制:

  • 使用 | 连接多个模式时不能使用绑定模式
  • 绑定模式不能嵌套出现在其他模式中
  • 绑定的变量是不可变的

3.4 Tuple 模式(Tuple Pattern)

用于匹配 tuple 值:

cangjie 复制代码
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" // 匹配成功
        case (name, 100) => "${name} is 100 years old"
        case (_, _) => "someone"
    }
    println(s)
}

执行结果:

csharp 复制代码
Alice is 24 years old

限制:

  • 同一个 tuple 模式中不允许引入多个名称相同的绑定模式

3.5 类型模式(Type Pattern)

用于判断运行时类型:

cangjie 复制代码
open class Base {
    var a: Int64
    public init() {
        a = 10
    }
}

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

func testTypePattern() {
    var d = Derived()
    var r = match (d) {
        case b: Base => b.a  // 匹配成功
        case _ => 0
    }
    println("r = ${r}")
}

main() {
    testTypePattern()
}

执行结果:

ini 复制代码
r = 20

3.6 Enum 模式(Enum Pattern)

用于匹配枚举值:

cangjie 复制代码
enum TimeUnit {
    | Year(UInt64)
    | Month(UInt64)
}

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

执行结果:

复制代码
x has 24 months

重要要求:

  • 使用 match 表达式匹配枚举值时,要求覆盖所有构造器
  • 可以通过 case _ 来覆盖未明确处理的情况

3.7 模式的嵌套组合

模式可以嵌套组合使用:

cangjie 复制代码
enum TimeUnit {
    | Year(UInt64)
    | Month(UInt64)
}

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

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

执行结果:

sql 复制代码
Set year 2022

4. 模式的 Refutability

4.1 概念定义

  • Refutable 模式:有可能和待匹配值不匹配的模式
  • Irrefutable 模式:总是可以和待匹配值匹配的模式

4.2 各种模式的 Refutability

4.2.1 常量模式

常量模式是 refutable 模式:

cangjie 复制代码
func constPat(x: Int64) {
    match (x) {
        case 1 => "one"
        case 2 => "two"
        case _ => "_"
    }
}

4.2.2 通配符模式

通配符模式是 irrefutable 模式:

cangjie 复制代码
func wildcardPat(x: Int64) {
    match (x) {
        case _ => "_"
    }
}

4.2.3 绑定模式

绑定模式是 irrefutable 模式:

cangjie 复制代码
func varPat(x: Int64) {
    match (x) {
        case a => "x = ${a}"
    }
}

4.2.4 Tuple 模式

Tuple 模式是 irrefutable 模式,当且仅当其包含的每个模式都是 irrefutable 模式:

cangjie 复制代码
func tuplePat(x: (Int64, Int64)) {
    match (x) {
        case (1, 2) => "(1, 2)"      // refutable
        case (a, 2) => "(${a}, 2)"   // refutable
        case (a, b) => "(${a}, ${b})" // irrefutable
    }
}

4.2.5 类型模式

类型模式是 refutable 模式:

cangjie 复制代码
interface I {}
open class Base <: I {}
class Derived <: Base {}

func typePat(x: I) {
    match (x) {
        case a: Derived => "Derived"
        case b: Base => "Base"
        case _ => "Other"
    }
}

4.2.6 Enum 模式

Enum 模式是 irrefutable 模式,当且仅当它对应的枚举类型中只有一个有参构造器,且 enum 模式中包含的其他模式也是 irrefutable 模式:

cangjie 复制代码
enum E1 {
    A(Int64)
}

enum E2 {
    B(Int64) | C(Int64)
}

func enumPat1(x: E1) {
    match (x) {
        case A(1) => "A(1)"    // refutable
        case A(a) => "A(${a})" // irrefutable
    }
}

func enumPat2(x: E2) {
    match (x) {
        case B(b) => "B(${b})" // refutable
        case C(c) => "C(${c})" // refutable
    }
}

5. Option 类型

5.1 Option 类型概述

Option 类型是仓颉语言中处理"可能有值,也可能没有值"情况的标准方式。

5.2 Option 类型定义

cangjie 复制代码
enum Option<T> {
    | Some(T)
    | None
}

5.3 简写语法

使用 ? 前缀:

cangjie 复制代码
let a: Option<Int64> = Some(100)
let b: ?Int64 = Some(100)        // 等价于 Option<Int64>
let c: Option<String> = Some("Hello")
let d: ?String = None

5.4 自动封装

当明确需要 Option<T> 类型时,可以直接传入 T 类型的值:

cangjie 复制代码
let a: Option<Int64> = 100       // 自动封装为 Some(100)
let b: ?Int64 = 100              // 自动封装为 Some(100)
let c: Option<String> = "100"    // 自动封装为 Some("100")

5.5 None 的使用

cangjie 复制代码
let a = None<Int64>  // a: Option<Int64>
let b = None<Bool>   // b: Option<Bool>

6. 模式在其他地方的使用

6.1 变量定义中的模式

cangjie 复制代码
// Enum 模式
enum RedColor {
    Red(Int64)
}

main() {
    // 通配符模式
    let _ = 100

    // 绑定模式
    let x = 100

    // Tuple 模式
    let (x1, y) = (100, 200)

    let Red(red) = Red(0)

    println("x1 = ${x1}, y = ${y}, red = ${red}")
}

6.2 for in 表达式中的模式

cangjie 复制代码
// Enum 模式
enum RedColor {
    Red(Int64)
}

main() {
    // 通配符模式
    for (_ in 1..5) {
        println("0")
    }

    // 绑定模式
    for (i in 1..5) {
        println(i)
    }

    // Tuple 模式
    for ((i, j) in [(1, 2), (3, 4), (5, 6)]) {
        println("Sum = ${i + j}")
    }

    for (Red(r) in [Red(10), Red(20), Red(30)]) {
        println("r = ${r}")
    }
}

重要限制:

  • 只有 irrefutable 的模式才能在变量定义和 for in 表达式中使用
  • 允许的模式包括:通配符模式、绑定模式、irrefutable tuple 模式和 irrefutable enum 模式

7. 实际应用示例

7.1 状态机实现

cangjie 复制代码
enum State {
    | Idle
    | Loading
    | Success(String)
    | Error(String)
}

func processState(state: State) {
    match (state) {
        case Idle => println("系统空闲")
        case Loading => println("正在加载...")
        case Success(data) => println("成功: ${data}")
        case Error(msg) => println("错误: ${msg}")
    }
}

main() {
    let states = [Idle, Loading, Success("数据加载完成"), State.Error("网络错误")]
    for (state in states) {
        processState(state)
    }
}

7.2 表达式求值器

cangjie 复制代码
enum Expr {
    | Num(Int64)
    | Add(Expr, Expr)
    | Sub(Expr, Expr)
    | Mul(Expr, Expr)
    | Div(Expr, Expr)
}

func evaluate(expr: Expr): Int64 {
    match (expr) {
        case Num(n) => n
        case Add(left, right) => evaluate(left) + evaluate(right)
        case Sub(left, right) => evaluate(left) - evaluate(right)
        case Mul(left, right) => evaluate(left) * evaluate(right)
        case Div(left, right) =>
            let rightVal = evaluate(right)
            if (rightVal == 0) {
                throw Exception("除零错误")
            }
            evaluate(left) / rightVal
    }
}

main() {
    let expr = Add(
        Mul(Num(5), Num(3)),
        Sub(Num(10), Num(2))
    )
    let result = evaluate(expr)
    println("表达式结果: ${result}")
}

7.3 错误处理

cangjie 复制代码
enum Result<T, E> {
    | Ok(T)
    | Err(E)
}

func divide(a: Int64, b: Int64): Result<Int64, String> {
    if (b == 0) {
        return Err("除零错误")
    }
    return Ok(a / b)
}

func handleDivision(a: Int64, b: Int64) {
    let result = divide(a, b)
    match (result) {
        case Ok(value) => println("除法结果: ${value}")
        case Err(message) => println("错误: ${message}")
    }
}

main() {
    handleDivision(10, 2)
    handleDivision(10, 0)
}

8. 最佳实践和注意事项

8.1 枚举设计原则

  1. 语义清晰:枚举构造器的名称应该清晰表达其含义
  2. 参数合理:为构造器添加必要的参数,避免过度设计
  3. 穷尽性:确保模式匹配覆盖所有可能的情况

8.2 模式匹配最佳实践

  1. 使用通配符 :在最后一个 case 中使用 _ 确保穷尽性
  2. 模式顺序:将具体的模式放在前面,通用的模式放在后面
  3. 避免重复 :使用 | 连接多个相似的模式

8.3 性能考虑

  1. 编译时优化:编译器会优化模式匹配,通常比 if-else 链更高效
  2. 穷尽性检查:编译时检查确保不会遗漏任何情况

8.4 常见陷阱

  1. 非穷尽匹配:忘记处理某些情况会导致编译错误
  2. 绑定模式冲突 :在 | 连接的模式中使用绑定模式
  3. 类型不匹配:模式类型与待匹配值类型不兼容

参考资料

相关推荐
HarderCoder43 分钟前
重学仓颉-7类与接口完全指南:从基础到高级特性
harmonyos
li理5 小时前
鸿蒙应用开发完全指南:深度解析UIAbility、页面与导航的生命周期
前端·harmonyos
HarderCoder6 小时前
重学仓颉-5结构体(Struct)完全指南:从基础到高级用法
harmonyos
HarmonyOS小助手7 小时前
【推荐+1】HarmonyOS官方模板优秀案例 (第4期:餐饮行业 · 美食菜谱)
harmonyos·鸿蒙·鸿蒙生态
HarderCoder8 小时前
重学仓颉-4函数系统完全指南
harmonyos
HarderCoder13 小时前
重学仓颉-3基本数据类型详解:从理论到实践的全面指南
harmonyos
鸿蒙小灰13 小时前
鸿蒙OpenCV移植技术要点
opencv·harmonyos
鸿蒙先行者13 小时前
鸿蒙分布式能力调用失败解决方案及案例
分布式·harmonyos
大雷神1 天前
鸿蒙中应用闪屏解决方案
华为·harmonyos