NOTE阅读文档版本:
语言规约 Cangjie-0.53.18-Spec
具体开发指南 Cangjie-LTS-1.0.4
在阅读 了解仓颉的语言规约时, 难免会涉及到一些仓颉的示例代码, 但 我们对仓颉并不熟悉, 所以可以用 仓颉在线体验 快速验证
有条件当然可以直接 配置 Canjie-SDK
WARNING博主在此之前, 基本只接触过 C/C++语言, 对大多现代语言都没有了解, 所以在阅读过程中遇到相似的概念, 难免会与 C/C++中的相似概念作类比, 见谅
且, 本系列是文档阅读, 而不是仓颉的零基础教学, 所以如果要跟着阅读的话最好有一门编程语言的开发经验
WARNING在阅读仓颉编程语言的开发指南之前, 已经大概阅读了一遍 仓颉编程语言的语言规约, 已经对仓颉编程语言有了一个大概的了解
所以在阅读开发指南时, 不会对类似: 类、函数、结构体、接口等解释起来较为复杂名称 做出解释
此样式内容, 表示文档原文内容
枚举类型和模式匹配
模式的Refutability
模式可以分为两类:
refutable模式和irrefutable模式在类型匹配的前提下, 当一个模式有可能和待匹配值不匹配时, 称此模式为
refutable模式; 反之, 当一个模式总是可以和待匹配值匹配时, 称此模式为irrefutable模式
Refutability翻译为可证伪性
仓颉将模式分为两类: 可证伪(refutable) 和 不可证伪(irrefutable)
并解释, 在类型匹配的前提下:
-
refutable可证伪, 表示 待匹配值可能存在无法匹配成功的情况 -
irrefutable不可证伪, 表示 待匹配值总能匹配成功的情况
对于上述介绍的各种模式, 规定如下:
常量模式是
refutable模式例如, 下例中第一个
case中的1和 第二个case中的2都有可能和x的值不相等func constPat(x: Int64) {match (x) {case 1 => "one"case 2 => "two"case _ => "_"}}
常量模式一定为refutable的, 即 可证伪的
因为你无法列举出所有的字面量, 更别说一定匹配成功了
通配符模式是
irrefutable模式例如, 下例中无论
x的值是多少,_总能和其匹配func wildcardPat(x: Int64) {match (x) {case _ => "_"}}绑定模式是
irrefutable模式例如, 下例中无论
x的值是多少, 绑定模式a总能和其匹配func varPat(x: Int64) {match (x) {case a => "x = ${a}"}}
通配符模式和绑定模式, 是irrefutable不可证伪的
因为他们总能匹配任意值
Tuple模式是irrefutable模式, 当且仅当其包含的每个模式都是irrefutable模式例如, 下例中
(1, 2)和(a, 2)都有可能和x的值不匹配, 所以它们是refutable模式, 而(a, b)可以匹配任何x的值, 所以它是irrefutable模式func tuplePat(x: (Int64, Int64)) {match (x) {case (1, 2) => "(1, 2)"case (a, 2) => "(${a}, 2)"case (a, b) => "(${a}, ${b})"}}
Tuple模式, 可以是refutable可证伪的, 也可以是irrefutable不可证伪的
因为Tuple模式依赖于其他模式, 所以 当且仅当Tuple模式包含的每个模式都是irrefutable的时, Tuple模式是irrefutable的
类型模式是
refutable模式例如, 下例中(假设
Base是Derived的父类, 并且Base实现了接口I),x的运行时类型有可能既不是Base也不是Derived, 所以a: Derived和b: Base均是refutable模式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"}}
类型模式是refutable可证伪的
因为, 可能存在待匹配目标为接口类型, 此时可能存在match中无法匹配待匹配目标的类型
enum模式是irrefutable模式, 当且仅当它对应的enum类型中只有一个有参构造器, 且enum模式中包含的其他模式也是irrefutable模式例如, 对于下例中的
E1和E2定义, 函数enumPat1中的A(1)是refutable模式,A(a)是irrefutable模式; 而函数enumPat2中的B(b)和C(c)均是refutable模式enum E1 {A(Int64)}enum E2 {B(Int64) | C(Int64)}func enumPat1(x: E1) {match (x) {case A(1) => "A(1)"case A(a) => "A(${a})"}}func enumPat2(x: E2) {match (x) {case B(b) => "B(${b})"case C(c) => "C(${c})"}}
enum模式 与Tuple模式类似, 都 可以是refutable可证伪的, 也可以是irrefutable不可证伪的, 因为他们都是要依赖其他模式的
但enum还有一点不同的是, match表达式中enum模式通常需要覆盖目标enum类型的所有构造器
所以enum如果要是irrefutable的 还有其他条件, 当且仅当enum类型中只有一个有参构造器, 且包含的每个模式都是irrefutable的时, enum模式是irrefutable的
match表达式
match表达式的定义
仓颉支持两种
match表达式, 第一种是包含待匹配值的match表达式, 第二种是不含待匹配值的match表达式
含匹配值的
match表达式:main() {let x = 0match (x) {case 1 => let r1 = "x = 1"print(r1)case 0 => let r2 = "x = 0" // Matchedprint(r2)case _ => let r3 = "x != 1 and x != 0"print(r3)}}
match表达式以关键字match开头, 后跟要匹配的值(如上例中的x,x可以是任意表达式), 接着是定义在一对花括号内的若干case分支每个
case分支以关键字case开头,case之后是一个模式或多个由|连接的相同种类的模式(如上例中的1、0、_都是模式, 详见 模式概述 章节)
match表达式的语法即为:
match (待匹配值) { case 模式1 => "" case 模式2 | 模式2 => ""}match表达式的case分支是用于匹配模式的, case之后的模式 可以使用|连接同种模式
模式之后可以接一个可选的
pattern guard, 表示本条case匹配成功后额外需要满足的条件,pattern guard使用where cond表示, 要求表达式cond的类型为Bool接着是一个
=>,=>之后即本条case分支匹配成功后需要执行的操作, 可以是一系列表达式、变量和函数定义(新定义的变量或函数的作用域从其定义处开始到下一个case之前结束), 如上例中的变量定义和
match表达式执行时, 依次将match之后的表达式与每个case中的模式进行匹配一旦匹配成功, 则执行
=>之后的代码, 然后退出match表达式的执行(意味着不会再去匹配它之后的case)关于匹配成功, 如果有
pattern guard, 也需要where之后的表达式的值为true; 如果case中有多个由|连接的模式, 只要待匹配值和其中一个模式匹配, 则认为匹配成功如果匹配不成功, 则继续与它之后的
case中的模式进行匹配, 直到匹配成功(match表达式可以保证一定存在匹配的case分支)上例中, 因为
x的值等于0, 所以会和第二条case分支匹配(此处使用的是常量模式, 匹配的是值是否相等, 详见 常量模式 章节), 最后输出x = 0编译并执行上述代码, 输出结果为:
x = 0
case模式之后, 还可以接一个pattern guard, 即where cond
所以, match表达式的语法可以是:
match (待匹配值) { case 模式1 where 条件 => ""}pattern guard中的条件, 必须是Bool类型
在匹配成功之后, 才会进行pattern guard判断, 如果模式是绑定模式, 那么条件中可以使用绑定的变量
match表达式要求 所有匹配必须是穷尽(exhaustive) 的, 意味着 待匹配表达式的所有可能取值都应该被考虑到当
match表达式非穷尽, 或者编译器判断不出是否穷尽时, 均会编译报错, 换言之, 所有case分支(包含pattern guard)所覆盖的取值范围的并集, 应该包含待匹配表达式的所有可能取值常用的确保
match表达式穷尽的方式是在最后一个case分支中使用通配符模式_, 因为_可以匹配任何值
match表达式的穷尽性保证了一定存在 和 待匹配值相匹配 的case分支下面的例子将编译报错, 因为所有的
case并没有覆盖x的所有可能取值:func nonExhaustive(x: Int64) {match (x) {case 0 => print("x = 0")case 1 => print("x = 1")case 2 => print("x = 2")}}如果被匹配值的类型包含
enum类型, 且该enum为non-exhaustive enum(包含无名构造器的enum), 则其在匹配时 需要使用可匹配所有构造器的模式, 如通配符模式_和绑定模式enum T {| Red | Green | Blue | ...}func foo(a: T) {match (a) {case Red => 0case Green => 1case Blue => 2case _ => -1}}func bar(a: T) {match (a) {case Red => 0case k => -1 // 简单绑定模式}}func baz(a: T) {match (a) {case Red => 0case k: T => -1 // 带嵌套类型模式的绑定模式}}
仓颉match表达式, 要求待匹配项必须能够匹配成功, 即 待匹配表达式的所有可能取值都应该被考虑到
即 使用match表达式进行模式匹配, 必须存在至少一个case分支能够匹配成功
如果待匹配项是enum类型, 那么同样需要手动匹配所有构造器, 如果enum存在...无名构造器, 一般就用 通配符_模式 或 绑定模式
当然, 其他类型也可以使用通配符_模式 和 绑定模式进行 匹配所有可能的取值
在
case分支的模式之后, 可以使用pattern guard进一步对匹配出来的结果进行判断在下面的例子中(使用到了
enum模式, 详见enum模式 章节), 当RGBColor的构造器的参数值大于等于0时, 输出它们的值, 当参数值小于0时, 认为它们的值等于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)}编译执行上述代码, 输出结果为:
Green = 0
pattern guard是在匹配成功之后才进行判断的
没有匹配值的
match表达式:main() {let x = -1match {case x > 0 => print("x > 0")case x < 0 => print("x < 0") // Matched.case _ => print("x = 0")}}与包含待匹配值的
match表达式相比, 关键字match之后并没有待匹配的表达式并且**
case之后不再是pattern**, 而是类型为Bool的表达式(上述代码中的x > 0和x < 0)或者_(表示true), 当然,case中也不再有pattern guard无匹配值的
match表达式执行时, 依次判断case之后的表达式的值, 直到遇到值为true的case分支:一旦某个
case之后的表达式值等于true, 则执行此case中=>之后的代码, 然后退出match表达式的执行(意味着不会再去判断该case之后的其他case)上例中, 因为
x的值等于-1, 所以第二条case分支中的表达式(即x < 0)的值等于true, 执行print("x < 0")编译并执行上述代码, 输出结果为:
x < 0
从文档来看, 无匹配值的match表达式, 更大的用处是 针对可见变量的分支判断, 实际类似if-else
文档中的例子, 实际可以等价于:
let x = -1if (x > 0) { print("x > 0")}else if (x < 0) { print("x < 0")}else { print("x = 0")}match表达式的类型
对于
match表达式(无论是否有匹配值):
在上下文有明确的类型要求时, 要求每个
case分支中=>之后的代码块的类型是上下文所要求的类型的子类型在上下文没有明确的类型要求时,
match表达式的类型是每个case分支中=>之后的代码块的类型的最小公共父类型当
match表达式的值没有被使用时, 其类型为Unit, 不要求各分支的类型有最小公共父类型下面分别举例说明
let x = 2let s: String = match (x) {case 0 => "x = 0"case 1 => "x = 1"case _ => "x != 0 and x != 1" // Matched}上面的例子中, 定义变量
s时, 显式地标注了其类型为String, 属于上下文类型信息明确的情况, 因此要求每个case的=>之后的代码块的类型均是String的子类型, 显然上例中=>之后的字符串类型的字面量均满足要求再来看一个没有上下文类型信息的例子:
let x = 2let s = match (x) {case 0 => "x = 0"case 1 => "x = 1"case _ => "x != 0 and x != 1" // Matched}上例中, 定义变量
s时, 未显式标注其类型, 因为每个case的=>之后的代码块的类型均是String, 所以match表达式的类型是String, 进而可确定s的类型也是String
match表达式的类型, 与仓颉其他表达式的类型是一致的
如果match表达式的值被使用了:
-
如果上下文有要求, 每个
case分支 的最终类型都要是上下文要求的子类型 -
如果没有要求, 那么 就是每个
case分支的最小公共父类型
如果没有被使用, 就没有任何要求, 类型就是Unit
其他使用模式的地方
模式除了可以在
match表达式中使用外, 还可以使用在变量定义(等号左侧是一个模式)和for in表达式(for关键字和in关键字之间是一个模式)中但是, 并不是所有的模式都能使用在变量定义和
for in表达式中, 只有irrefutable的模式才能在这两处被使用, 所以只有通配符模式、绑定模式、irrefutable tuple模式和irrefutable enum模式是允许的
仓颉的模式, 并不是只能在match表达式中使用, 还可以使用在变量定义和for-in表达式
但只有不可证伪的模式才能在其他地方使用, 即 通配符_模式, 绑定模式 以及 不可证伪的Tuple以及enum模式
定义变量时, =左边是一个模式(变量名)
for-in表达式中, for和in之间是一个模式
变量定义和
for in表达式中使用通配符模式的例子如下:main() {let _ = 100for (_ in 1..5) {println("0")}}上例中, 变量定义时使用了通配符模式, 表示定义了一个没有名字的变量(当然此后也就没办法对其进行访问)
for in表达式中使用了通配符模式, 表示不会将1..5中的元素与某个变量绑定(当然循环体中就无法访问1..5中元素值)编译执行上述代码, 输出结果为:
0000
定义变量, 可以使用通配符替代普通变量, 表示忽略目标
for-in表达式, for和in之间也可以使用通配符, 也表示忽略目标
变量定义和
for in表达式中使用绑定模式的例子如下:main() {let x = 100println("x = ${x}")for (i in 1..5) {println(i)}}上例中, 变量定义中的
x以及for in表达式中的i都是绑定模式编译执行上述代码, 输出结果为:
x = 1001234
仓颉的变量定义, 本身就是一个绑定模式
for-in表达式最基础的使用方式, 也是一个绑定模式
变量定义和
for in表达式中使用irrefutable tuple模式的例子如下: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}")}}上例中, 变量定义时使用了
tuple模式, 表示对(100, 200)进行解构并分别和x与y进行绑定, 效果上相当于定义了两个变量x和y
for in表达式中使用了tuple模式, 表示依次将[(1, 2), (3, 4), (5, 6)]中的tuple类型的元素取出, 然后解构并分别和i与j进行绑定, 循环体中输出i + j的值编译执行上述代码, 输出结果为:
x = 100y = 200Sum = 3Sum = 7Sum = 11
irrefutable Tuple模式, 就是只包含通配符_模式和绑定模式的Tuple模式
定义变量时, 可以使用irrefutable Tuple模式, 但此时并不是简单的通配符模式或绑定模式, 同时包含元组的解构
let (x, y) = (100, 200)println("x = ${x}")println("y = ${y}")并不是简单定义了一个元组, 而是 在定义变量时 通过irrefutable Tuple模式, 元组模式匹配 并 解构元组, 定义了两个不同的变量
for ((i, j) in [(1, 2), (3, 4), (5, 6)]) { println("Sum = ${i + j}")}for-in表达式中也是同样的概念
变量定义和
for in表达式中使用irrefutable enum模式的例子如下: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}")}}上例中, 变量定义时使用了
enum模式, 表示对Red(0)进行解构并将构造器的参数值(即 0)与red进行绑定
for in表达式中使用了enum模式, 表示依次将[Red(10), Red(20), Red(30)]中的元素取出, 然后解构并将构造器的参数值与r进行绑定, 循环体中输出r的值编译执行上述代码, 输出结果为:
red = 0r = 10r = 20r = 30
irrefutable enum模式与irrefutable Tuple是类似的, 包含模式匹配 与 解构