枚举和 ADT 的翻译
编译器将枚举及其用例扩展为仅使用 Scala 其他语言功能的代码。因此,Scala 中的枚举是方便的语法糖,但它们对于理解 Scala 的核心并不是必需的。
现在,我们将详细解释枚举的扩展。首先,一些术语和符号约定
-
我们使用
E
作为枚举的名称,使用C
作为出现在E
中的用例的名称。 -
我们使用
<...>
表示在某些情况下可能为空的语法结构。例如,<value-params>
表示一个或多个参数列表(...)
或什么也没有。 -
枚举案例分为三类
- 类案例是带参数的案例,带类型参数部分
[...]
或一个或多个(可能为空)的参数部分(...)
。 - 简单案例是非泛型枚举的案例,既没有参数,也没有 extends 子句或主体。也就是说,它们仅由名称组成。
- 值案例是没有参数部分但有(可能生成的)
extends
子句和/或主体的案例。
- 类案例是带参数的案例,带类型参数部分
简单案例和值案例统称为单例案例。
简化规则表明类案例映射到 case 类,单例案例映射到 val
定义。
有九条简化规则。规则 (1) 简化枚举定义。规则 (2) 和 (3) 简化简单案例。规则 (4) 到 (6) 定义缺少 extends 子句的案例的 extends
子句。规则 (7) 到 (9) 定义带有 extends
子句的此类案例如何映射到 case class
或 val
。
-
一个
enum
定义enum E ... { <defs> <cases> }
扩展到一个
sealed abstract
类,该类扩展scala.reflect.Enum
特征和一个关联的伴随对象,其中包含根据规则 (2 - 8) 展开的已定义案例。枚举类以编译器生成的导入开头,该导入导入所有案例的名称<caseIds>
,以便可以在类中不带前缀使用它们。sealed abstract class E ... extends <parents> with scala.reflect.Enum { import E.{ <caseIds> } <defs> } object E { <cases> }
-
一个由逗号分隔的枚举名称列表组成的简单案例
case C_1, ..., C_n
扩展到
case C_1; ...; case C_n
原始案例上的任何修饰符或注释都扩展到所有展开的案例。
-
一个简单案例
case C
一个不采用类型参数的枚举
E
扩展到val C = $new(n, "C")
此处,
$new
是一个创建E
实例的私有方法(见下文)。 -
如果
E
是一个带类型参数的枚举V1 T1 >: L1 <: U1 , ... , Vn Tn >: Ln <: Un (n > 0)
其中每个方差
Vi
都是'+'
或'-'
,那么一个简单案例case C
扩展到
case C extends E[B1, ..., Bn]
其中
Bi
是Li
(如果Vi = '+'
)和Ui
(如果Vi = '-'
)。然后使用规则 (8) 进一步重写此结果。不允许具有非变量类型参数的枚举的简单案例(但是带有显式extends
子句的值案例是允许的) -
一个没有 extends 子句的类案例
case C <type-params> <value-params>
一个不采用类型参数的枚举
E
扩展到case C <type-params> <value-params> extends E
然后使用规则 (9) 进一步重写此结果。
-
如果
E
是具有类型参数Ts
的枚举,则类案例既没有类型参数也没有 extends 子句case C <value-params>
扩展到
case C[Ts] <value-params> extends E[Ts]
然后使用规则 (9) 进一步重写此结果。对于具有类型参数自身的类案例,需要显式给出 extends 子句。
-
如果
E
是具有类型参数Ts
的枚举,则类案例没有类型参数但具有 extends 子句case C <value-params> extends <parents>
扩展到
case C[Ts] <value-params> extends <parents>
前提是至少一个参数
Ts
在<value-params>
中的参数类型或<parents>
中的类型参数中被提及。 -
值案例
case C extends <parents>
扩展到
E
的伴随对象中的值定义val C = new <parents> { <body>; def ordinal = n }
其中
n
是伴随对象中案例的序数,从 0 开始。匿名类还实现了它从Enum
继承的抽象Product
方法。如果值案例在
<parents>
的类型参数中引用封闭enum
的类型参数,则会出错。 -
类案例
case C <params> extends <parents>
扩展类似于
E
的伴随对象中的最终案例类final case class C <params> extends <parents>
枚举案例定义以下形式的
ordinal
方法def ordinal = n
其中
n
是伴随对象中案例的序数,从 0 开始。如果值案例在
<params>
中的参数类型或<parents>
的类型参数中引用封闭enum
的类型参数,则会出错,除非该参数已经是案例的类型参数,即参数名称在<params>
中定义。编译器生成的枚举案例的
apply
和copy
方法case C(ps) extends P1, ..., Pn
被特别处理。只要该类型仍与应用点处的预期类型兼容,apply 方法的调用
C(ts)
就会被指定为基础类型P1 & ... & Pn
(删除任何 透明特征)。C
的copy
方法的调用t.copy(ts)
以相同方式处理。
具有单例案例的枚举的翻译
定义一个或多个单例案例的枚举 E
(可能是泛型的)将在其伴随对象中定义以下附加合成成员(其中 E'
表示用通配符替换任何类型参数的 E
)
- 方法
valueOf(name: String): E'
。它返回标识符为name
的单例案例值。 - 一个方法
values
,它返回一个Array[E']
,其中包含E
定义的所有单例 case 值,按照其定义的顺序。
如果 E
至少包含一个简单 case,则其伴生对象将另外定义
-
一个私有方法
$new
,它定义一个新的简单 case 值,并具有给定的序数和名称。可以将此方法视为按如下方式定义。private def $new(_$ordinal: Int, $name: String) = new E with runtime.EnumValue: def ordinal = _$ordinal override def productPrefix = $name // if not overridden in `E` override def toString = $name // if not overridden in `E`
匿名类还实现了它从 Enum
继承的抽象 Product
方法。仅当枚举未从 java.lang.Enum
扩展(因为 Scala 枚举不会扩展 java.lang.Enum
,除非明确指定)时,才会生成 ordinal
方法。如果确实如此,则无需生成 ordinal
,因为 java.lang.Enum
定义了它。同样,无需覆盖 toString
,因为 java.lang.Enum
中已根据 name
对其进行了定义。最后,当 E
扩展 java.lang.Enum
时,productPrefix
将调用 this.name
。
枚举 case 的作用域
enum
中的 case 的处理方式类似于辅助构造函数。它既不能使用 this
访问封闭的 enum
,也不能使用简单标识符访问其值参数或实例成员。
即使已翻译的枚举 case 位于枚举的伴生对象中,通过 this
或简单标识符引用此对象或其成员也是非法的。编译器在封闭伴生对象的作用域中对枚举 case 进行类型检查,但会将任何此类非法访问标记为错误。
Java 兼容枚举的翻译
Java 兼容枚举是扩展 java.lang.Enum
的枚举。翻译规则与上述相同,但有本节中定义的保留。
对于 Java 兼容枚举,具有类 case 是编译时错误。
诸如 case C
的 case 会扩展到 @static val
,而不是 val
。这允许它们作为枚举类型的静态字段生成,从而确保它们以与 Java 枚举相同的方式表示。
其他规则
-
不允许未从枚举案例中产生的普通案例类扩展
scala.reflect.Enum
。这确保了枚举的唯一案例是其中显式声明的案例。 -
如果枚举案例具有
extends
子句,则枚举类必须是扩展的类之一。