写点什么

仓颉之枚举类型与模式匹配的冒险之旅

  • 2025-08-22
    中国香港
  • 本文字数:6468 字

    阅读完需:约 21 分钟

仓颉之枚举类型与模式匹配的冒险之旅

1 概述

1.1 背景介绍

仓颉编程语言作为一款面向全场景应用开发的现代编程语言,通过现代语言特性的集成、全方位的编译优化和运行时实现、以及开箱即用的 IDE 工具链支持,为开发者打造友好开发体验和卓越程序性能。


案例结合代码体验,让大家更直观的了解仓颉语言中的枚举类型与模式匹配。

1.2 适用对象

  • 个人开发者

  • 高校学生

1.3 案例时间

本案例总时长预计 40 分钟。

1.4 案例流程


说明:


① 进入华为开发者空间,登录云主机;② 使用 CodeArts IDE for Cangjie 编程和运行仓颉代码。

仓颉之枚举类型与模式匹配的冒险之旅 👈👈👈体验最新完整版案例,点击这里。

2 运行测试环境准备

2.1 开发者空间配置

面向广大开发者群体,华为开发者空间提供一个随时访问的“开发桌面云主机”、丰富的“预配置工具集合”和灵活使用的“场景化资源池”,开发者开箱即用,快速体验华为根技术和资源。


领取云主机后可以直接进入华为开发者空间工作台界面,点击打开云主机 > 进入桌面连接云主机。没有领取在开发者空间根据指引领取配置云主机即可,云主机配置参考 1.5 资源总览



2.2 创建仓颉程序

点击桌面 CodeArts IDE for Cangjie,打开编辑器,点击新建工程,保持默认配置,点击创建


产物类型说明


  • executable,可执行文件;

  • static,静态库,是一组预先编译好的目标文件的集合;

  • dynamic,动态库,是一种在程序运行时才被加载到内存中的库文件,多个程序共享一个动态库副本,而不是像静态库那样每个程序都包含一份完整的副本。


2.3 运行仓颉工程

创建完成后,打开 src/main.cj,参考下面代码简单修改后,点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。


package demo// 第一个仓颉程序main(): Int64 {    println("hello world")    println("你好,仓颉!")    return 0}
复制代码


(* 注意:后续替换 main.cj 文件代码时,package demo 保留)


(* 仓颉注释语法:// 符号之后写单行注释,也可以在一对 /* 和 */ 符号之间写多行注释)



到这里,我们第一个仓颉程序就运行成功啦!后面案例中的示例代码都可以放到 main.cj 文件中进行执行,接下来我们继续探索仓颉语言。

3 枚举类型与模式匹配

3.1 枚举类型

enum 类型提供了通过列举一个类型的所有可能取值来定义此类型的方式。仓颉中的 enum 类型可以理解为函数式编程语言中的代数数据类型。


enum 的定义:


定义 enum 时需要把它所有可能的取值一一列出,称这些值为 enum 的构造器(或者 constructor)。


具体代码如下:


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


enum 类型的定义以关键字 enum 开头,接着是 enum 的名字,之后是定义在一对花括号中的 enum 体,enum 体中定义了若干构造器,多个构造器之间使用 | 进行分隔。


enum 中的构造器还可以携带若干(至少一个)参数,称为有参构造器。


具体代码如下:


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


仓颉支持同一个 enum 中定义多个同名构造器。


具体代码如下:


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


注意:enum 只能定义在源文件的顶层作用域。


enum 的使用:


具体代码如下:


enum RGBColor {    | Red | Green | Blue(UInt8)}main() {    let r = RGBColor.Red    let g = Green    let b = Blue(100)}
复制代码


可以省略类型名,但是可能类型名、变量名、函数名发生冲突,建议使用 enum 类型名来使用 enum 构造器。

3.2 Option 类型

Option 类型使用 enum 定义,它包含两个构造器:Some 和 None。


其中,Some 会携带一个参数,表示有值,None 不带参数,表示无值。当需要表示某个类型可能有值,也可能没有值的时候,可选择使用 Option 类型。


Option 类型被定义为一个泛型 enum 类型,具体代码如下:


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


其中,Some 构造器的参数类型就是类型形参 T,当 T 被实例化为不同的类型时,会得到不同的 Option 类型,例如:Option<Int64>、Option<String>等。


Option 类型还有一种简单的写法:在类型名前加 ?。


例如,?Int64 等价于 Option<Int64>,?String 等价于 Option<String>


具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


main() {    // 变量c和d是等价的    let c: ?String = "Hello"    let d: Option<String> = Some("Hello")    println("${c}")    println("${d}")}
复制代码


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。


3.3 match 表达式

match 表达式的定义:


仓颉支持两种 match 表达式,第一种是包含待匹配值的 match 表达式,第二种是不含待匹配值的 match 表达式。


含匹配值的 match 表达式,下例中,因为 x 的值等于 0,所以会和第二条 case 分支匹配,最后输出 x = 0。


具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


main() {    let x = 0    match (x) {        case 1 => let r1 = "x = 1"                  print(r1)        case 0 => let r2 = "x = 0"                   print(r2)        case _ => let r3 = "x != 1 and x != 0"                  print(r3)    }}
复制代码


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。


备注:终端窗口打印的内容,若存在警告信息,忽略即可,不影响程序运行。



没有匹配值的 match 表达式,下例中,因为 x 的值等于 -1,所以第二条 case 分支中的表达式(即 x < 0)的值等于 true,执行 print("x < 0")。


具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


main() {    let x = -1    match {        case x > 0 => print("x > 0")        case x < 0 => print("x < 0")         case _ => print("x = 0")    }}
复制代码


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。


备注:终端窗口打印的内容,若存在警告信息,忽略即可,不影响程序运行。


3.4 模式概述

对于包含匹配值的 match 表达式,case 之后支持哪些模式决定了 match 表达式的表达能力,本节中将依次介绍仓颉支持的模式,包括:常量模式、通配符模式、绑定模式、tuple 模式、类型模式和 enum 模式。


常量模式:


下面的例子中,根据 score 的值(假设 score 只能取 0 到 100 间被 10 整除的值),输出考试成绩的等级。


具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


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


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。


备注:终端窗口打印的内容,若存在警告信息,忽略即可,不影响程序运行。



通配符模式:


通配符模式使用下划线 _ 表示,可以匹配任意值。通配符模式通常作为最后一个 case 中的模式,用来匹配其他 case 未覆盖到的情况,如常量模式中匹配 score 值的示例中,最后一个 case 中使用 _ 来匹配无效的 score 值。


绑定模式:


绑定模式使用 id 表示,id 是一个合法的标识符。与通配符模式相比,绑定模式同样可以匹配任意值,但绑定模式会将匹配到的值与 id 进行绑定,在 => 之后可以通过 id 访问其绑定的值。


下面的例子中,最后一个 case 中使用了绑定模式,用于绑定非 0 值。


具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


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


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。


备注:终端窗口打印的内容,若存在警告信息,忽略即可,不影响程序运行。



Tuple 模式:


Tuple 模式用于 tuple 值的匹配,它的定义和 tuple 字面量类似:(p_1, p_2, ..., p_n),区别在于这里的 p_1 到 p_n(n 大于等于 2)是模式而不是表达式。


tuple 模式使用,具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


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


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。


备注:终端窗口打印的内容,若存在警告信息,忽略即可,不影响程序运行。



类型模式:


类型模式用于判断一个值的运行时类型是否是某个类型的子类型。


假设有如下两个类,Base 和 Derived,并且 Derived 是 Base 的子类,Base 的无参构造函数中将 a 的值设置为 10,Derived 的无参构造函数中将 a 的值设置为 20。


具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


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


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。


备注:终端窗口打印的内容,若存在警告信息,忽略即可,不影响程序运行。



enum 模式:


enum 模式用于匹配 enum 类型的实例,它的定义和 enum 的构造器类似:无参构造器 C 或有参构造器 C(p_1, p_2, ..., p_n),构造器的类型前缀可以省略,区别在于这里的 p_1 到 p_n(n 大于等于 1)是模式。


下例中,展示了 enum 模式的使用,因为 x 的构造器是 Year,所以会和第一个 case 匹配,具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


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


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。


备注:终端窗口打印的内容,若存在警告信息,忽略即可,不影响程序运行。



模式的嵌套组合:


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


下面的代码展示了不同模式嵌套组合使用,具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


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 _ => ()    }}
复制代码


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。


3.5 模式的 Refutability

备注:refutable 中文意思是可反驳的;irrefutable 是不能反驳的,无可辩驳的。


模式可以分为两类:refutable 模式和 irrefutable 模式。在类型匹配的前提下,当一个模式有可能和待匹配值不匹配时,称此模式为 refutable 模式;反之,当一个模式总是可以和待匹配值匹配时,称此模式为 irrefutable 模式。


refutable 模式:常量模式、类型模式。


irrefutable 模式:通配符模式、绑定模式、Tuple 模式、enum 模式。


常量模式是 refutable 模式。例如,下例中第一个 case 中的 1 和第二个 case 中的 2 都有可能和 x 的值不相等。


具体代码如下:


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


绑定模式是 irrefutable 模式。例如,下例中无论 x 的值是多少,绑定模式 a 总能和其匹配,具体代码如下:


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

3.6 if-let 表达式

if-let 表达式首先对条件中 <- 右侧的表达式进行求值,如果此值能匹配 <- 左侧的模式,则执行 if 分支,否则执行 else 分支(可省略)。


具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


main() {    let result = Option<Int64>.Some(2023)    if (let Some(value) <- result) {        println("操作成功,返回值为:${value}")    } else {        println("操作失败")    }}
复制代码


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。


3.7 while-let 表达式

while-let 表达式首先对条件中 <- 右侧的表达式进行求值,如果此值能匹配 <- 左侧的模式,则执行循环体,然后重复执行此过程。如果模式匹配失败,则结束循环,继续执行 while-let 表达式之后的代码。


具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


import std.random.*// 此函数模拟在通信中接收数据,获取数据可能失败func recv(): Option<UInt8> {    let number = Random().nextUInt8()    if (number < 128) {        return Some(number)    }    return None}main() {    // 模拟循环接收通信数据,如果失败就结束循环    while (let Some(data) <- recv()) {        println(data)    }    println("receive failed")}
复制代码


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可能打印出以下内容。


备注:由于使用了 Random 函数,每次运行结果可能不一样。


3.8 其他使用模式的地方

模式除了可以在 match 表达式中使用外,还可以使用在变量定义和 for in 表达式中。但是,并不是所有的模式都能使用在变量定义和 for in 表达式中,只有 irrefutable 的模式才能在这两处被使用,所以只有通配符模式、绑定模式、irrefutable tuple 模式和 irrefutable enum 模式是允许的。


  • 变量定义和 for in 表达式中使用通配符模式。


具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


main() {    let _ = 100    for (_ in 1..5) {        println("0")    }}
复制代码


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。



  • 变量定义和 for in 表达式中使用绑定模式。

  • 具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


main() {    let x = 100    println("x = ${x}")    for (i in 1..5) {        println(i)    }}
复制代码


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。



  • 变量定义和 for in 表达式中使用 irrefutable tuple 模式。

  • 具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


main() {    let (x, y) = (100, 200)    println("x = ${x}")    println("y = ${y}")    for ((i, j) in [(1, 2), (3, 4), (5, 6)]) {        println("Sum = ${i + j}")    }}
复制代码


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。



  • 变量定义和 for in 表达式中使用 irrefutable enum 模式。

  • 具体代码操作如下:


Step1:复制以下代码,替换 main.cj 文件中的代码。(保留 package)


enum RedColor {    Red(Int64)}main() {    let Red(red) = Red(0)    println("red = ${red}")    for (Red(r) in [Red(10), Red(20), Red(30)]) {        println("r = ${r}")    }}
复制代码


Step2:点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。



至此,仓颉之枚举类型与模式匹配的冒险之旅案例内容已全部完成。


如果想了解更多仓颉编程语言知识可以访问:https://cangjie-lang.cn/


用户头像

提供全面深入的云计算技术干货 2020-07-14 加入

生于云,长于云,让开发者成为决定性力量

评论

发布
暂无评论
仓颉之枚举类型与模式匹配的冒险之旅_华为开发者_华为云开发者联盟_InfoQ写作社区