NOTE阅读文档版本:
语言规约 Cangjie-0.53.18-Spec
具体开发指南 Cangjie-LTS-1.0.4
在阅读 了解仓颉的语言规约时, 难免会涉及到一些仓颉的示例代码, 但 我们对仓颉并不熟悉, 所以可以用 仓颉在线体验 快速验证
有条件当然可以直接 配置 Canjie-SDK
WARNING博主在此之前, 基本只接触过 C/C++语言, 对大多现代语言都没有了解, 所以在阅读过程中遇到相似的概念, 难免会与 C/C++中的相似概念作类比, 见谅
且, 本系列是文档阅读, 而不是仓颉的零基础教学, 所以如果要跟着阅读的话最好有一门编程语言的开发经验
WARNING在阅读仓颉编程语言的开发指南之前, 已经大概阅读了一遍 仓颉编程语言的语言规约, 已经对仓颉编程语言有了一个大概的了解
所以在阅读开发指南时, 不会对类似: 类、函数、结构体、接口等解释起来较为复杂名称 做出解释
此样式内容, 表示文档原文内容
枚举类型和模式匹配
模式概述
对于包含匹配值的
match表达式,case之后支持哪些模式决定了match表达式的表达能力本节中将依次介绍仓颉支持的模式,包括:常量模式、通配符模式、绑定模式、
tuple模式、类型模式和enum模式
match表达式, 还未阅读到开发指南文档中的相关部分
但, 从已阅读的语法规约中, match是一种模式匹配的表达式
如果非要类比的话, 与C/C++中与之最相似的语法应该是switch-case, 但要复杂的多
而match表达式, case之后支持的模式决定了表达式的表达能力
什么是模式?
模式, 是一种匹配目标值的结构, 通常用在match表达式、定义变量等场景
可以类比C/C++中的switch-case来理解, switch是只能匹配整型的一种结构
而仓颉中, 不同模式对应不同的目标类型, 不过像switch这样匹配整型的结构, 在仓颉中 属于 常量模式
模式并不是一种单独的语法, 而是与特定的匹配结构结合使用
常量模式
常量模式可以是整数字面量、浮点数字面量、字符字面量、布尔字面量、字符串字面量(不支持字符串插值)、
Unit字面量在包含匹配值的
match表达式(参见[match表达式])中使用常量模式时,要求常量模式表示的值的类型与待匹配值的类型相同,匹配成功的条件是待匹配的值与常量模式表示的值相等下面的例子中,根据
score的值(假设score只能取0到100间被10整除的值),输出考试成绩的等级:main() {let score = 90let level = match (score) {case 0 | 10 | 20 | 30 | 40 | 50 => "D"case 60 => "C"case 70 | 80 => "B"case 90 | 100 => "A" // Matchedcase _ => "Not a valid score"}println(level)}编译执行上述代码,输出结果为:
A
在模式匹配的目标是静态类型为
Rune的值时,Rune字面量和单字符字符串字面量都可用于表示Rune类型字面量的常量patternfunc translate(n: Rune) {match (n) {case "A" => 1case "B" => 2case "C" => 3case _ => -1}}main() {println(translate(r"C"))}编译执行上述代码,输出结果为:
3在模式匹配的目标是静态类型为
Byte的值时,一个表示ASCII字符的字符串字面量可用于表示Byte类型字面量的常量patternfunc translate(n: Byte) {match (n) {case "1" => 1case "2" => 2case "3" => 3case _ => -1}}main() {println(translate(51)) // UInt32(r'3') == 51}编译执行上述代码,输出结果为:
3
常量模式, 只会出现在match表达式中
它用于对比 变量 是否与 目标字面量 匹配, 可用字面量有: 整数字面量、浮点数字面量、字符字面量、布尔字面量、字符串字面量(不支持字符串插值)、Unit 字面量
使用语法为:
match (value) { case 目标字面量1 => 匹配成功执行语句 case 目标字面量2 => 匹配成功执行语句 case 目标字面量3 | 目标字面量4 => 匹配成功执行语句 ...}匹配的字面量之间, 可以使用|进行连接, 表示 多目标匹配
被|连接的, value只要与其中一个相等就算匹配成功
通配符模式
通配符模式使用下划线
_表示,可以匹配任意值通配符模式通常作为最后一个
case中的模式,用来匹配其他case未覆盖到的情况,如常量模式中匹配score值的示例中,最后一个case中使用_来匹配无效的score值
通配符模式, 不止会出现在match表达式中
通配符_可以匹配任意值, 如果match中存在_就不会出现匹配不不到的情况: 如果match前面都没有匹配到, 绝对会匹配到_
上面已经有了通配符模式的例子:
match (value) { case _ => ""}只要走到case _, 就会匹配成功
绑定模式
绑定模式使用
id表示,id是一个合法的标识符与通配符模式相比,绑定模式同样可以匹配任意值,但绑定模式会将匹配到的值与
id进行绑定,在=>之后可以通过id访问其绑定的值下面的例子中,最后一个
case中使用了绑定模式,用于绑定非0值:main() {let x = -10let y = match (x) {case 0 => "zero"case n => "x is not zero and x = ${n}" // Matched}println(y)}编译执行上述代码,输出结果为:
x is not zero and x = -10
绑定模式, 同样不止出现在match表达式中
绑定模式与通配符模式类似, 能匹配到任意值, 即 只要走到这里, 就能匹配成功
但通配符使用_, 而绑定模式使用一个合法的标识符
通配符模式, 匹配成功之后, 在=>之后不能尝试访问匹配成功的值, 因为没有绑定到标识符
但 绑定模式不同, 绑定模式存在标识符, 所以在=>之后, 可以通过标识符 访问匹配的值:
match (value) { case num => "the value is ${num}"}只要走到case num, 就能匹配成功, 且value绑定到num, 即 num就是value
TIP绑定模式, 绑定行为与 值类型或引用类型的赋值、传参行为保持一致
使用
|连接多个模式时不能使用绑定模式,也不可嵌套出现在其他模式中,否则会报错:main() {let opt = Some(0)match (opt) {case x | x => {} // Error, 在由 '|' 连接的模式中不能引入绑定模式case Some(x) | Some(x) => {} // Error, 在由 '|' 连接的模式中不能引入绑定模式case x: Int64 | x: String => {} // Error, 在由 '|' 连接的模式中不能引入绑定模式}}绑定模式
id相当于新定义了一个名为id的不可变变量(其作用域从引入处开始到该case结尾处),因此在=>之后无法对id进行修改例如,下例中最后一个
case中对n的修改是不允许的main() {let x = -10let y = match (x) {case 0 => "zero"case n => n = n + 0 // Error, 'n' 不能被修改"x is not zero"}println(y)}对于每个
case分支,=>之后变量作用域级别 与case后=>前引入的变量 作用域级别相同, 在=>之后再次引入相同名字会触发重定义错误例如:
main() {let x = -10let y = match (x) {case 0 => "zero"case n => let n = 0 // Error, 重定义println(n)"x is not zero"}println(y)}注意:
当模式的
identifier为enum构造器时,该模式会被当成enum模式进行匹配,而不是绑定模式(关于enum模式,详见enum模式章节)enum RGBColor {| Red | Green | Blue}main() {let x = Redlet y = match (x) {case Red => "red" // 'Red' 在这是 enum 模式case _ => "not red"}println(y)}编译执行上述代码,输出结果为:
red
绑定模式不能使用|连接, 因为|连接 只要匹配其中一个, 就能执行=>后面的语句
如果是绑定模式, 则后面应该是可以访问绑定标识符的, 但如果|连接了, 绑定标识符可能就无效了, 所以应该被禁止
绑定模式的标识符, 绑定匹配成功之后 相当于定义了一个不可变变量
且 =>之后执行语句的作用域 与 case后的作用域是同等级的, 所以 =>之后不允许再定义 与绑定标识符同名的变量
如果绑定模式的标识符是enum构造器时, 会被当作enum模式, 而不是绑定模式
Tuple 模式
Tuple模式用于tuple值的匹配,它的定义和tuple字面量类似:(p_1, p_2, ..., p_n),区别在于这里的p_1到p_n(n大于等于2)是模式(可以是本章节中介绍的任何模式,多个模式间使用逗号分隔)而不是表达式例如,
(1, 2, 3)是一个包含三个常量模式的tuple模式,(x, y, _)是一个包含两个绑定模式,一个通配符模式的tuple模式给定一个
tuple值tv和一个tuple模式tp,当且仅当tv每个位置处的值均能与tp中对应位置处的模式相匹配,才称tp能匹配tv例如,
(1, 2, 3)仅可以匹配tuple值(1, 2, 3),(x, y, _)可以匹配任何三元tuple值下面的例子中,展示了
tuple模式的使用: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"是一个常量模式,而'age'是一个绑定模式case (name, 100) => "${name} is 100 years old"case (_, _) => "someone"}println(s)}编译执行上述代码,输出结果为:
Alice is 24 years old同一个
tuple模式中不允许引入多个名称相同的绑定模式例如,下例中最后一个
case中的case (x, x)是不合法的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 (x, x) => "someone" // Error, 不能引入同名的变量模式,将重定义错误}println(s)}
Tuple模式, 不止出现在match表达式中
了解了上面几个模式之后, Tuple模式其实很好理解
Tuple就是元组, 普通元组字面量可以存储数据
而Tuple模式, 与元组字面量语法类似, 只不过Tuple模式的()中, 应该是其他模式: 常量模式、通配符模式、绑定模式、类型模式等等
即, Tuple模式里可以是其他任意模式
但, Tuple模式, 必须匹配到()内的所有模式, 才算匹配成功
相关的语法为
match (value) { case (mode, mode, mode) => ""}当value是一个元组, 且能够与目标模式匹配时, 匹配成功
类型模式
类型模式用于 判断一个值的运行时类型是否是某个类型的子类型
类型模式有两种形式:
_: Type(嵌套一个通配符模式_)和id: Type(嵌套一个绑定模式id),它们的区别是后者会发生变量绑定,而前者不会对于待匹配值
v和类型模式id: Type(或_: Type)首先 判断
v的运行时类型是否是Type的子类型,若 成立则视为匹配成功,否则视为匹配失败如匹配成功,则将
v的类型转换为Type并与id进行绑定(对于_: Type,不存在绑定这一操作)假设有如下两个类,
Base和Derived,并且Derived是Base的子类,Base的无参构造函数中将a的值设置为10,Derived的无参构造函数中将a的值设置为20:open class Base {var a: Int64public 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}")}编译执行上述代码,输出结果为:
r = 20下面的代码展示了使用类型模式但类型模式匹配失败的例子:
open class Base {var a: Int64public init() {a = 10}}class Derived <: Base {public init() {a = 20}}main() {var b = Base()var r = match (b) {case d: Derived => d.a // Type pattern match failed.case _ => 0 // Matched.}println("r = ${r}")}编译执行上述代码,输出结果为:
r = 0
类型模式, 只会出现在match表达式中
类型模式是为了匹配目标的运行时类型, 是否为目标类型的子类型
语法为:
match (value) { case ident: Type => "${ident}" case _: Type => "${ident}"}当value运行时类型为Type的子类型时, 匹配成功
enum 模式
enum模式用于匹配enum类型的实例,它的定义和enum的构造器类似:无参构造器C或有参构造器C(p_1, p_2, ..., p_n),构造器的类型前缀可以省略,区别在于这里的p_1到p_n(n大于等于1)是模式例如,
Some(1)是一个包含一个常量模式的enum模式,Some(x)是一个包含一个绑定模式的enum模式给定一个
enum实例ev和一个enum模式ep,当且仅当ev的构造器名字和ep的构造器名字相同,且ev参数列表中每个位置处的值均能与ep中对应位置处的模式相匹配,才称ep能匹配ev例如,
Some("one")仅可以匹配Option<String>类型的Some构造器Option<String>.Some("one"),Some(x)可以匹配任何Option类型的Some构造器下面的例子中,展示了
enum模式的使用,因为x的构造器是Year,所以会和第一个case匹配: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)}编译执行上述代码,输出结果为:
x has 24 months使用
|连接多个enum模式:enum TimeUnit {| Year(UInt64)| Month(UInt64)}main() {let x = Year(2)let s = match (x) {case Year(0) | Year(1) | Month(_) => "Ok" // Okcase 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)}
enum模式, 虽然文档中说明很多, 但是并不复杂
其实就是尝试匹配 目标enum类型的构造器, 要求与构造器名和参数列表保持一致
但, enum模式里的参数列表就和Tuple模式的()一样, 是其他模式了
enum也可以使用|连接, 同样此时enum模式构造器的参数中不能存在匹配模式和类型模式
enum模式的语法在match里为:
match (value) { case EnumConstructor => "" case EnumConstructor(mode, mode) => ""}value需要是目标enum类型的值, 匹配时要与目标构造器进行匹配
使用
match表达式匹配enum值时,要求case之后的模式要覆盖待匹配enum类型中的所有构造器,如果未做到完全覆盖,编译器将报错:enum RGBColor {| Red | Green | Blue}main() {let c = Greenlet cs = match (c) { // Error, Not all constructors of RGBColor are covered.case Red => "Red"case Green => "Green"}println(cs)}可以通过加上
case Blue来实现完全覆盖,也可以在match表达式的最后通过使用case _来覆盖其他case未覆盖的到的情况,如:enum RGBColor {| Red | Green | Blue}main() {let c = Bluelet cs = match (c) {case Red => "Red"case Green => "Green"case _ => "Other" // Matched.}println(cs)}上述代码的执行结果为:
Other
enum模式在match表达式中使用时, 要保证match表达式的case匹配, 能够覆盖目标enum类型的所有构造器
可以使用通配符模式实现, 也可以手动case所有构造器实现
模式的嵌套组合
Tuple模式和enum模式可以嵌套任意模式下面的代码展示了不同模式嵌套组合使用:
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 _ => ()}}编译并执行上述代码,输出结果为:
Set year 2022
Tuple模式和enum模式, 是可以嵌套使用其他所有模式的
或者说, Tuple模式和enum模式, 本身就是结合其他模式一起使用的