在 GitHub 上编辑此页面

代数数据类型

enum 概念足够通用,还可以支持代数数据类型 (ADT) 及其广义版本 (GADT)。下面是一个示例,说明如何将 Option 类型表示为 ADT

enum Option[+T]:
  case Some(x: T)
  case None

此示例引入了一个 Option 枚举,它具有协变类型参数 T,由两个情况组成,SomeNoneSome 使用值参数 x 进行参数化。它是编写扩展 Option 的案例类的速记。由于 None 未进行参数化,因此将其视为普通枚举值。

上面示例中省略的 extends 子句也可以显式给出

enum Option[+T]:
  case Some(x: T) extends Option[T]
  case None       extends Option[Nothing]

请注意,None 值的父类型推断为 Option[Nothing]。通常,枚举类的所有协变类型参数在编译器生成的 extends 子句中最小化,而所有逆变类型参数最大化。如果 Option 是非变异的,则需要显式给出 None 的 extends 子句。

对于普通枚举值,enum 的情况都在 enum 的伴生对象中定义。因此,它是 Option.SomeOption.None,除非使用导入“拉出”定义

scala> Option.Some("hello")
val res1: t2.Option[String] = Some(hello)

scala> Option.None
val res2: t2.Option[Nothing] = None

请注意,上述表达式的类型始终为 Option。通常情况下,枚举情况构造函数应用程序的类型将扩展到基础枚举类型,除非需要更具体的类型。这是与普通情况类的一个细微差别。构成情况的类确实存在,并且可以通过使用 new 直接构造它们或显式提供预期类型来揭示它们。

scala> new Option.Some(2)
val res3: Option.Some[Int] = Some(2)
scala> val x: Option.Some[Int] = Option.Some(3)
val res4: Option.Some[Int] = Some(3)

与所有其他枚举一样,ADT 可以定义方法。例如,这里再次出现 Option,其中包含一个 isDefined 方法和一个伴随对象中的 Option(...) 构造函数。

enum Option[+T]:
  case Some(x: T)
  case None

  def isDefined: Boolean = this match
    case None => false
    case _    => true

object Option:

  def apply[T >: Null](x: T): Option[T] =
    if x == null then None else Some(x)

end Option

枚举和 ADT 已被表示为两个不同的概念。但是,由于它们共享相同的语法结构,因此可以将它们视为频谱的两端,并且完全可以构建混合体。例如,以下代码给出了 Color 的实现,它具有三个枚举值或带有采用 RGB 值的参数化情况。

enum Color(val rgb: Int):
  case Red   extends Color(0xFF0000)
  case Green extends Color(0x00FF00)
  case Blue  extends Color(0x0000FF)
  case Mix(mix: Int) extends Color(mix)

枚举的参数方差

默认情况下,具有类型参数的参数化枚举情况将复制其父级的类型参数以及任何方差符号。与往常一样,在它们是变体时谨慎使用类型参数非常重要,如下所示

以下 View 枚举具有协变类型参数 T 和一个单一情况 Refl,它表示将类型 T 映射到它自身的函数

enum View[-T]:
  case Refl(f: T => T)

Refl 的定义不正确,因为它在函数类型的协变结果位置使用了协变类型 T,导致出现以下错误

-- Error: View.scala:2:12 --------
2 |   case Refl(f: T => T)
  |             ^^^^^^^^^
  |contravariant type T occurs in covariant position in type T => T of value f
  |enum case Refl requires explicit declaration of type T to resolve this issue.

由于 Refl 没有声明显式参数,因此在编译器中看起来如下所示

enum View[-T]:
  case Refl[/*synthetic*/-T1](f: T1 => T1) extends View[T1]

编译器已为 Refl 推断出协变类型参数 T1,它在 View 中跟随 T。现在我们可以清楚地看到 Refl 需要声明自己的非变体类型参数才能正确输入 f,并且可以通过对 Refl 进行以下更改来纠正错误

enum View[-T]:
-  case Refl(f: T => T)
+  case Refl[R](f: R => R) extends View[R]

在上面,类型 R 被选为 Refl 的参数,以突出显示它与 View 中的类型 T 有不同的含义,但任何名称都可以。

经过一些进一步的更改,可以给出 View 的更完整的实现,如下所示,并将其用作函数类型 T => U

enum View[-T, +U] extends (T => U):
  case Refl[R](f: R => R) extends View[R, R]

  final def apply(t: T): U = this match
    case refl: Refl[r] => refl.f(t)

枚举的语法

语法更改分为两类:枚举定义和枚举中的案例。更改如下指定,相对于 此处 给出的 Scala 语法

  1. 枚举定义如下定义

    TmplDef   ::=  `enum' EnumDef
    EnumDef   ::=  id ClassConstr [`extends' [ConstrApps]] EnumBody
    EnumBody  ::=  [nl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’
    EnumStat  ::=  TemplateStat
                |  {Annotation [nl]} {Modifier} EnumCase
    
  2. 枚举的案例如下定义

    EnumCase  ::=  `case' (id ClassConstr [`extends' ConstrApps]] | ids)
    

参考

有关更多信息,请参阅 问题 #1970