此文档页面特定于 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
方法)。