仓颉语言 -- 枚举类型和模式匹配

1、枚举类型

本节介绍仓颉中的 enum 类型。enum 类型提供了通过列举一个类型的所有可能取值来定义此类型的方式

在很多语言中都有 enum 类型(或者称枚举类型),但是不同语言中的 enum 类型的使用方式和表达能力均有所差异,仓颉中的 enum 类型可以理解为函数式编程语言中的代数数据类型(Algebraic Data Types

接下来,首先介绍如何定义和使用 enum,然后介绍如何使用模式匹配使得 enum 取不同值时执行不同的操作,最后介绍一个名为 Option 的常用 enum 类型,用于表示某个类型的实例要么有值要么没值。

1.1 enum 的定义

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

enum RGBColor {
   
    | Red | Green | Blue
}

enum 类型的定义以关键字 enum 开头,接着是 enum 的名字,之后是定义在一对花括号中的 enum 体,enum 体中定义了若干构造器,多个构造器之间使用 | 进行分隔(第一个构造器之前的| 是可选的)。上例中定义了一个名为 RGBColor 的 enum 类型,它有 3 个构造器:Red、Green 和 Blue,分别表示 RGB 色彩模式中的红色、绿色和蓝色。

上述 enum 中的构造器还可以携带若干(至少一个)参数,称为有参构造器。例如,可以为 Red、Green 和 Blue 设置一个 UInt8 的类型的参数,用来表示每个颜色的亮度级别:

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

仓颉支持同一个 enum 中定义多个同名构造器,但是要求这些构造器的参数个数不同(认为没有参数的构造器的参数个数等于 0),例如:

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

enum 支持递归定义,例如,下面的例子中使用 enum 定义了一种表达式(即 Expr),此表达式只能有 3 种形式:单独的一个数字 Num(携带一个 Int64 类型的参数)、加法表达式 Add(携带两个 Expr 类型的参数)、减法表达式 Sub(携带两个 Expr 类型的参数)。对于 Add 和 Sub 这两个构造器,其参数中递归地使用到了 Expr 自身。

enum Expr {
   
    | Num(Int64)
    | Add(Expr, Expr)
    | Sub(Expr, Expr)
}

另外,在 enum 体中还可以定义一系列成员函数、操作符函数(详见操作符重载章节)和成员属性(详见属性章节),但是要求构造器、成员函数、成员属性之间不能重名。例如,下面的例子在 RGBColor 中定义了一个名为 printType 的函数,它会输出字符串 RGBColor:

  • enum 只能定义在源文件顶层
  • 当 enum 和 struct 类型存在互递归关系时,且 enum 类型作为 Option 的类型参数,可能存在编译错误。

1.2 enum 的使用

定义了 enum 类型之后,就可以创建此类型的实例(即 enum 值),enum 值只能取 enum 类型定义中的一个构造器。enum 没有构造函数,可以通过 类型名.构造器,或者直接使用构造器的方式来构造一个 enum 值(对于有参构造器,需要传实参)

下例中,RGBColor 中定义了三个构造器,其中有两个无参构造器(Red 和 Green)和一个有参构造器(Blue(UInt8)),main 中定义了三个 RGBColor 类型的变量 r,g 和 b,其中,r 的值使用 RGBColor.Red 进行初始化,g 的值直接使用 Green 进行初始化,b 的值使用 Blue(100) 进行初始化:

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

main() {
   
    let r = RGBColor.Red
    let g = Green
    let b = Blue(100)
}

当省略类型名时,enum 构造器的名字可能和类型名、变量名、函数名发生冲突。此时必须加上 enum 类型名来使用 enum 构造器,否则只会选择同名的类型、变量、函数定义。

下面的例子中,只有构造器 Blue(UInt8) 可以不带类型名使用,Red 和 Green(UInt8) 皆会因为名字冲突而不能直接使用,必须加上类型名 RGBColor。

let Red = 1

func Green(g: UInt8) {
   
    return g
}

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

let r1 = Red                 // Will choose 'let Red'
let r2 = RGBColor.Red        // Ok: constructed by enum type name

let g1 = Green(100)          // Will choose 'func Green'
let g2 = RGBColor.Green(100) // Ok: constructed by enum type name

let b = Blue(100)            // Ok: can be uniquely identified as an enum constructor

如下的例子中,只有构造器 Blue 会因为名称冲突而不能直接使用,必须加上类型名 RGBColor。

class Blue {
   }

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

let r = Red                 // Ok: constructed by enum type name

let g = Green(100)          // Ok: constructed by enum type name

let b = Blue(100)           // Will choose constructor of 'class Blue' and report an error

2、Option 类型

Option 类型使用 enum 定义,它包含两个构造器:SomeNone。其中,Some 会携带一个参数,表示有值,None 不带参数,表示无值当需要表示某个类型可能有值,也可能没有值的时候,可选择使用 Option 类型

Option 类型被定义为一个泛型 enum 类型,定义如下(这里我们仅需要知道尖括号中的 T 是一个类型形参,当 T 为不同类型时会得到不同的 Option 类型即可。关于泛型的详细介绍,可参见泛):

enum Option<T> {
   
    | Some(T)
    | None
}

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

Option 类型还有一种简单的写法:在类型名前加 ?。也就是说,对于任意类型 Ty,?Ty 等价于 Option<Ty>。例如,?Int64 等价于 Option<Int64>?String 等价于 Option<String> 等等。

下面的例子展示了如何定义 Option 类型的变量:

let a: Option<Int64> = Some(100)
let b: ?Int64 = Some(100)
let c: Option<String> = Some("Hello")
let d: ?String = None

另外,虽然 TOption<T> 是不同的类型,但是当明确知道某个位置需要的是 Option<T> 类型的值时,可以直接传一个 T 类型的值,编译器会用 Option<T> 类型的 Some 构造器将 T 类型的值封装成 Option<T> 类型的值(注意:这里并不是类型转换。例如,下面的定义是合法的(等价于上例中变量 a,b 和 c 的定义):

let a: Option<Int64> = 100
let b: ?Int64 = 100
let c: Option<String> = "100"

在上下文没有明确的类型要求时,无法使用 None 直接构造出想要的类型,此时应使用 None<T> 这样的语法来构造 Option<T> 类型的数据,例如

let a = None<Int64> // a: Option<Int64>
let b = None<Bool> // b: Option<Bool>

最后,关于 Option 的使用,请参见使用 Option

3、模式概述

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

3.1 常量模式

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

在包含匹配值的 match 表达式中使用常量模式时,要求常量模式表示的值的类型与待匹配值的类型相同,匹配成功的条件是待匹配的值与常量模式表示的值相等

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

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)
}

编译执行上述代码,输出结果为:

A
  • 在模式匹配的目标是静态类型为 Rune 的值时,Rune 字面量和单字符字符串字面量都可用于表示 Rune 类型字面量的常量 pattern
func translate(n: Rune) {
   
    match (n) {
   
        case "一" 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值