代數資料類型
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。