在 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