代数数据类型
enum
概念足够通用,还可以支持代数数据类型 (ADT) 及其广义版本 (GADT)。下面是一个示例,说明如何将 Option
类型表示为 ADT
enum Option[+T]:
case Some(x: T)
case None
此示例引入了一个 Option
枚举,它具有协变类型参数 T
,由两个情况组成,Some
和 None
。Some
使用值参数 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.Some
和 Option.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 语法
-
枚举定义如下定义
TmplDef ::= `enum' EnumDef EnumDef ::= id ClassConstr [`extends' [ConstrApps]] EnumBody EnumBody ::= [nl] ‘{’ [SelfType] EnumStat {semi EnumStat} ‘}’ EnumStat ::= TemplateStat | {Annotation [nl]} {Modifier} EnumCase
-
枚举的案例如下定义
EnumCase ::= `case' (id ClassConstr [`extends' ConstrApps]] | ids)
参考
有关更多信息,请参阅 问题 #1970。