枚举类型

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

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

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

enum 的定义

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

enum RGBColor {
    | Red | Green | Blue
}

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

上述 enum 中的构造器还可以携带若干(至少一个)参数,称为有参构造器。例如,可以为 RedGreenBlue 设置一个 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 类型的参数)。对于 AddSub 这两个构造器,其参数中递归地使用到了 Expr 自身。

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

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

enum RGBColor {
    | Red | Green | Blue

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

注意:

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

enum 的使用

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

下例中,RGBColor 中定义了三个构造器,其中有两个无参构造器(RedGreen)和一个有参构造器(Blue(UInt8)),main 中定义了三个 RGBColor 类型的变量 rgb,其中,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) 可以不带类型名使用,RedGreen(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