在 GitHub 上編輯此頁面

枚舉和 ADT 的翻譯

編譯器會將列舉及其案例擴充為僅使用 Scala 其他語言功能的程式碼。因此,Scala 中的列舉是方便的語法糖,但它們並非理解 Scala 核心所必需的。

我們現在詳細說明列舉的擴充。首先,一些術語和符號慣例

  • 我們使用 E 作為列舉的名稱,並使用 C 作為出現在 E 中的案例的名稱。

  • 我們使用 <...> 表示在某些情況下可能為空的語法結構。例如,<value-params> 表示一個或多個參數清單 (...) 或完全沒有。

  • 列舉案例分為三類

    • 類別案例是那些帶有參數化的案例,它們帶有型別參數區段 [...] 或一個或多個(可能為空)參數區段 (...)
    • 簡單案例是非泛型列舉的案例,它們既沒有參數,也沒有 extends 子句或主體。也就是說,它們只包含一個名稱。
    • 值案例是不具有參數區段但具有(可能產生的)extends 子句和/或主體的所有案例。

簡單案例和值案例統稱為單例案例

去糖規則暗示類別案例會對應到案例類別,而單例案例會對應到 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 的列舉,則為沒有類型參數且沒有延伸子句的類別案例

    case C <value-params>
    

    會擴充到

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

    此結果接著使用規則 (9) 重新寫入。對於具有類型參數的類別案例,需要明確提供延伸子句。

  7. 如果 E 是具有類型參數 Ts 的列舉,則為沒有類型參數但具有延伸子句的類別案例

    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
    

    會特別處理。應用方法的呼叫 C(ts) 會被指定底層類型 P1 & ... & Pn(捨棄任何 透明特質),只要該類型在應用點仍與預期類型相容。Ccopy 方法呼叫 t.copy(ts) 會以相同方式處理。

單例案例列舉的翻譯

定義一個或多個單例案例的列舉 E(可能是泛型的)會在其伴隨物件中定義下列附加合成成員(其中 E' 表示 E,其任何類型參數都已替換為萬用字元)

  • 方法 valueOf(name: String): E'。它會傳回識別碼為 name 的單例案例值。
  • 方法 values,它會傳回 E 定義的所有單例案例值的 Array[E'],依其定義順序。

如果 E 包含至少一個簡單案例,其伴隨物件會另外定義

  • 私有方法 $new,它會定義一個新的簡單案例值,其序號和名稱已給定。此方法可以視為已定義如下。

    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

列舉案例的範圍

enum 中的案例會以類似於次要建構函數的方式處理。它無法使用 this 存取封裝的 enum,也無法使用簡單識別碼存取其值參數或實例成員。

即使已翻譯的列舉案例位於列舉的伴隨物件中,透過 this 或簡單識別碼參照此物件或其成員也是不允許的。編譯器會在封裝伴隨物件的範圍中類型檢查列舉案例,但會將任何此類非法存取標記為錯誤。

與 Java 相容的列舉的翻譯

與 Java 相容的列舉是延伸 java.lang.Enum 的列舉。翻譯規則與上述相同,但有本節定義的保留事項。

與 Java 相容的列舉具有類別案例是編譯時期錯誤。

例如 case C 的案例會擴充為 @static val,而不是 val。這允許它們作為列舉類型的靜態欄位產生,從而確保它們的表示方式與 Java 列舉相同。

其他規則

  • 未從列舉案例產生的常態案例類別不允許延伸 scala.reflect.Enum。這可確保列舉的唯一案例是明確宣告在其中的案例。

  • 如果列舉案例有 extends 子句,列舉類別必須是延伸的類別之一。