在 GitHub 上编辑此页面

枚举和 ADT 的翻译

编译器将枚举及其用例扩展为仅使用 Scala 其他语言功能的代码。因此,Scala 中的枚举是方便的语法糖,但它们对于理解 Scala 的核心并不是必需的。

现在,我们将详细解释枚举的扩展。首先,一些术语和符号约定

  • 我们使用 E 作为枚举的名称,使用 C 作为出现在 E 中的用例的名称。

  • 我们使用 <...> 表示在某些情况下可能为空的语法结构。例如,<value-params> 表示一个或多个参数列表 (...) 或什么也没有。

  • 枚举案例分为三类

    • 类案例是带参数的案例,带类型参数部分 [...] 或一个或多个(可能为空)的参数部分 (...)
    • 简单案例是非泛型枚举的案例,既没有参数,也没有 extends 子句或主体。也就是说,它们仅由名称组成。
    • 值案例是没有参数部分但有(可能生成的)extends 子句和/或主体的案例。

简单案例和值案例统称为单例案例

简化规则表明类案例映射到 case 类,单例案例映射到 val 定义。

有九条简化规则。规则 (1) 简化枚举定义。规则 (2) 和 (3) 简化简单案例。规则 (4) 到 (6) 定义缺少 extends 子句的案例的 extends 子句。规则 (7) 到 (9) 定义带有 extends 子句的此类案例如何映射到 case classval

  1. 一个 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> }
    
  2. 一个由逗号分隔的枚举名称列表组成的简单案例

    case C_1, ..., C_n
    

    扩展到

    case C_1; ...; case C_n
    

    原始案例上的任何修饰符或注释都扩展到所有展开的案例。

  3. 一个简单案例

    case C
    

    一个不采用类型参数的枚举 E 扩展到

    val C = $new(n, "C")
    

    此处,$new 是一个创建 E 实例的私有方法(见下文)。

  4. 如果 E 是一个带类型参数的枚举

    V1 T1 >: L1 <: U1 ,   ... ,    Vn Tn >: Ln <: Un      (n > 0)
    

    其中每个方差 Vi 都是 '+''-',那么一个简单案例

    case C
    

    扩展到

    case C extends E[B1, ..., Bn]
    

    其中 BiLi(如果 Vi = '+')和 Ui(如果 Vi = '-')。然后使用规则 (8) 进一步重写此结果。不允许具有非变量类型参数的枚举的简单案例(但是带有显式 extends 子句的值案例是允许的)

  5. 一个没有 extends 子句的类案例

    case C <type-params> <value-params>
    

    一个不采用类型参数的枚举 E 扩展到

    case C <type-params> <value-params> extends E
    

    然后使用规则 (9) 进一步重写此结果。

  6. 如果 E 是具有类型参数 Ts 的枚举,则类案例既没有类型参数也没有 extends 子句

    case C <value-params>
    

    扩展到

    case C[Ts] <value-params> extends E[Ts]
    

    然后使用规则 (9) 进一步重写此结果。对于具有类型参数自身的类案例,需要显式给出 extends 子句。

  7. 如果 E 是具有类型参数 Ts 的枚举,则类案例没有类型参数但具有 extends 子句

    case C <value-params> extends <parents>
    

    扩展到

    case C[Ts] <value-params> extends <parents>
    

    前提是至少一个参数 Ts<value-params> 中的参数类型或 <parents> 中的类型参数中被提及。

  8. 值案例

    case C extends <parents>
    

    扩展到 E 的伴随对象中的值定义

    val C = new <parents> { <body>; def ordinal = n }
    

    其中 n 是伴随对象中案例的序数,从 0 开始。匿名类还实现了它从 Enum 继承的抽象 Product 方法。

    如果值案例在 <parents> 的类型参数中引用封闭 enum 的类型参数,则会出错。

  9. 类案例

    case C <params> extends <parents>
    

    扩展类似于 E 的伴随对象中的最终案例类

    final case class C <params> extends <parents>
    

    枚举案例定义以下形式的 ordinal 方法

    def ordinal = n
    

    其中 n 是伴随对象中案例的序数,从 0 开始。

    如果值案例在 <params> 中的参数类型或 <parents> 的类型参数中引用封闭 enum 的类型参数,则会出错,除非该参数已经是案例的类型参数,即参数名称在 <params> 中定义。

    编译器生成的枚举案例的 applycopy 方法

    case C(ps) extends P1, ..., Pn
    

    被特别处理。只要该类型仍与应用点处的预期类型兼容,apply 方法的调用 C(ts) 就会被指定为基础类型 P1 & ... & Pn(删除任何 透明特征)。Ccopy 方法的调用 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 子句,则枚举类必须是扩展的类之一。