此文件頁面專屬於 Scala 3,可能涵蓋 Scala 2 中沒有的新概念。除非另有說明,此頁面中的所有程式碼範例都假設您使用的是 Scala 3。
代數資料類型 (ADT) 可以使用 enum
建構建立,因此我們在檢視 ADT 之前,會先簡要檢閱列舉。
列舉
列舉用於定義由一組命名值組成的類型
enum Color:
case Red, Green, Blue
可視為以下的簡寫
enum Color:
case Red extends Color
case Green extends Color
case Blue extends Color
參數
列舉可以參數化
enum Color(val rgb: Int):
case Red extends Color(0xFF0000)
case Green extends Color(0x00FF00)
case Blue extends Color(0x0000FF)
這樣,每個不同的變體都有值成員 rgb
,並指定對應的值
println(Color.Green.rgb) // prints 65280
自訂定義
列舉也可以有自訂定義
enum Planet(mass: Double, radius: Double):
private final val G = 6.67300E-11
def surfaceGravity = G * mass / (radius * radius)
def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity
case Mercury extends Planet(3.303e+23, 2.4397e6)
case Venus extends Planet(4.869e+24, 6.0518e6)
case Earth extends Planet(5.976e+24, 6.37814e6)
// 5 or 6 more planets ...
就像類別和 case
類別,您也可以為列舉定義伴隨物件
object Planet:
def main(args: Array[String]) =
val earthWeight = args(0).toDouble
val mass = earthWeight / Earth.surfaceGravity
for (p <- values)
println(s"Your weight on $p is ${p.surfaceWeight(mass)}")
代數資料類型 (ADT)
enum
概念夠通用,也可以支援 *代數資料類型* (ADT) 和其廣義版本 (GADT)。以下範例顯示如何將 Option
型別表示為 ADT
enum Option[+T]:
case Some(x: T)
case None
此範例建立一個 Option
列舉,其中包含一個共變型別參數 T
,由兩個情況組成,Some
和 None
。 Some
以值參數 x
參數化;這是寫入延伸 Option
的 case
類別的簡寫。由於 None
未參數化,因此將其視為一般的 enum
值。
前一個範例中省略的 extends
子句也可以明確給出
enum Option[+T]:
case Some(x: T) extends Option[T]
case None extends Option[Nothing]
與一般的 enum
值一樣,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
與其他列舉使用方式一樣,ADT 可以定義其他方法。例如,以下是 Option
,其伴隨物件中有一個 isDefined
方法和一個 Option(...)
建構函式
enum Option[+T]:
case Some(x: T)
case None
def isDefined: Boolean = this match
case None => false
case Some(_) => true
object Option:
def apply[T >: Null](x: T): Option[T] =
if (x == null) None else Some(x)
列舉和 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)
遞迴列舉
到目前為止,我們定義的所有列舉都包含不同變異的值或案例類別。列舉也可以是遞迴的,如下列編碼自然數的範例所示
enum Nat:
case Zero
case Succ(n: Nat)
例如,值 Succ(Succ(Zero))
表示一元編碼中的數字 2
。清單可以用非常相似的定義
enum List[+A]:
case Nil
case Cons(head: A, tail: List[A])
廣義代數資料類型 (GADT)
上述列舉符號非常簡潔,可用作建模資料類型的完美起點。由於我們可以隨時更明確,因此也可以表達更強大的類型:廣義代數資料類型 (GADT)。
以下是 GADT 的範例,其中類型參數 (T
) 指定儲存在方塊中的內容
enum Box[T](contents: T):
case IntBox(n: Int) extends Box[Int](n)
case BoolBox(b: Boolean) extends Box[Boolean](b)
在特定建構函式(IntBox
或 BoolBox
)上進行模式比對可復原型別資訊
def extract[T](b: Box[T]): T = b match
case IntBox(n) => n + 1
case BoolBox(b) => !b
只有在第一個情況下,才安全地傳回 Int
,因為我們從模式比對中得知輸入是 IntBox
。
反糖化列舉
概念上,列舉可以被認為是定義一個密封類別及其伴隨物件。讓我們看看上面 Color
列舉的反糖化
sealed abstract class Color(val rgb: Int) extends scala.reflect.Enum
object Color:
case object Red extends Color(0xFF0000) { def ordinal = 0 }
case object Green extends Color(0x00FF00) { def ordinal = 1 }
case object Blue extends Color(0x0000FF) { def ordinal = 2 }
case class Mix(mix: Int) extends Color(mix) { def ordinal = 3 }
def fromOrdinal(ordinal: Int): Color = ordinal match
case 0 => Red
case 1 => Green
case 2 => Blue
case _ => throw new NoSuchElementException(ordinal.toString)
請注意,上述反糖化已簡化,我們故意省略 一些細節。
雖然列舉可以使用其他建構手動編碼,但使用列舉更簡潔,並附帶一些額外的公用程式(例如 fromOrdinal
方法)。