diff --git a/_overviews/scala3-book/types-adts-gadts.md b/_overviews/scala3-book/types-adts-gadts.md index 1ee8fb48a6..46f8708ac6 100644 --- a/_overviews/scala3-book/types-adts-gadts.md +++ b/_overviews/scala3-book/types-adts-gadts.md @@ -8,41 +8,63 @@ previous-page: types-union next-page: types-variance --- - Algebraic Data Types (ADTs) can be created with the `enum` construct, so we’ll briefly review enumerations before looking at ADTs. ## Enumerations An _enumeration_ is used to define a type consisting of a set of named values: +{% tabs types-adts-gadts-1 %} +{% tab 'Scala 3 only' %} ```scala enum Color: case Red, Green, Blue ``` +{% endtab %} +{% endtabs %} + which can be seen as a shorthand for: + +{% tabs types-adts-gadts-2 %} +{% tab 'Scala 3 only' %} ```scala enum Color: case Red extends Color case Green extends Color case Blue extends Color ``` +{% endtab %} +{% endtabs %} + #### Parameters Enums can be parameterized: +{% tabs types-adts-gadts-3 %} +{% tab 'Scala 3 only' %} ```scala enum Color(val rgb: Int): case Red extends Color(0xFF0000) case Green extends Color(0x00FF00) case Blue extends Color(0x0000FF) ``` +{% endtab %} +{% endtabs %} + This way, each of the different variants has a value member `rgb` which is assigned the corresponding value: + +{% tabs types-adts-gadts-4 %} +{% tab 'Scala 3 only' %} ```scala println(Color.Green.rgb) // prints 65280 ``` +{% endtab %} +{% endtabs %} #### Custom Definitions Enums can also have custom definitions: +{% tabs types-adts-gadts-5 %} +{% tab 'Scala 3 only' %} ```scala enum Planet(mass: Double, radius: Double): @@ -55,9 +77,13 @@ enum Planet(mass: Double, radius: Double): case Earth extends Planet(5.976e+24, 6.37814e6) // 5 or 6 more planets ... ``` +{% endtab %} +{% endtabs %} Like classes and `case` classes, you can also define a companion object for an enum: +{% tabs types-adts-gadts-6 %} +{% tab 'Scala 3 only' %} ```scala object Planet: def main(args: Array[String]) = @@ -66,17 +92,23 @@ object Planet: for (p <- values) println(s"Your weight on $p is ${p.surfaceWeight(mass)}") ``` +{% endtab %} +{% endtabs %} ## Algebraic Datatypes (ADTs) The `enum` concept is general enough to also support _algebraic data types_ (ADTs) and their generalized version (GADTs). Here’s an example that shows how an `Option` type can be represented as an ADT: +{% tabs types-adts-gadts-7 %} +{% tab 'Scala 3 only' %} ```scala enum Option[+T]: case Some(x: T) case None ``` +{% endtab %} +{% endtabs %} This example creates an `Option` enum with a covariant type parameter `T` consisting of two cases, `Some` and `None`. `Some` is _parameterized_ with a value parameter `x`; this is a shorthand for writing a `case` class that extends `Option`. @@ -84,14 +116,20 @@ Since `None` is not parameterized, it’s treated as a normal `enum` value. The `extends` clauses that were omitted in the previous example can also be given explicitly: +{% tabs types-adts-gadts-8 %} +{% tab 'Scala 3 only' %} ```scala enum Option[+T]: case Some(x: T) extends Option[T] case None extends Option[Nothing] ``` +{% endtab %} +{% endtabs %} As with normal `enum` values, the cases of an `enum` are defined in the `enum`s companion object, so they’re referred to as `Option.Some` and `Option.None` (unless the definitions are “pulled out” with an import): +{% tabs types-adts-gadts-9 %} +{% tab 'Scala 3 only' %} ```scala scala> Option.Some("hello") val res1: t2.Option[String] = Some(hello) @@ -99,10 +137,14 @@ val res1: t2.Option[String] = Some(hello) scala> Option.None val res2: t2.Option[Nothing] = None ``` +{% endtab %} +{% endtabs %} As with other enumeration uses, ADTs can define additional methods. For instance, here’s `Option` again, with an `isDefined` method and an `Option(...)` constructor in its companion object: +{% tabs types-adts-gadts-10 %} +{% tab 'Scala 3 only' %} ```scala enum Option[+T]: case Some(x: T) @@ -116,6 +158,8 @@ object Option: def apply[T >: Null](x: T): Option[T] = if (x == null) None else Some(x) ``` +{% endtab %} +{% endtabs %} Enumerations and ADTs share the same syntactic construct, so they can be seen simply as two ends of a spectrum, and it’s perfectly possible @@ -124,6 +168,8 @@ For instance, the code below gives an implementation of `Color`, either with three enum values or with a parameterized case that takes an RGB value: +{% tabs types-adts-gadts-11 %} +{% tab 'Scala 3 only' %} ```scala enum Color(val rgb: Int): case Red extends Color(0xFF0000) @@ -131,46 +177,91 @@ enum Color(val rgb: Int): case Blue extends Color(0x0000FF) case Mix(mix: Int) extends Color(mix) ``` +{% endtab %} +{% endtabs %} #### Recursive Enumerations So far all the enumerations that we defined consisted of different variants of values or case classes. Enumerations can also be recursive, as illustrated in the below example of encoding natural numbers: + +{% tabs types-adts-gadts-12 %} +{% tab 'Scala 3 only' %} ```scala enum Nat: case Zero case Succ(n: Nat) ``` +{% endtab %} +{% endtabs %} + For example the value `Succ(Succ(Zero))` represents the number `2` in an unary encoding. Lists can be defined in a very similar way: +{% tabs types-adts-gadts-13 %} +{% tab 'Scala 3 only' %} ```scala enum List[+A]: case Nil case Cons(head: A, tail: List[A]) ``` +{% endtab %} +{% endtabs %} ## Generalized Algebraic Datatypes (GADTs) The above notation for enumerations is very concise and serves as the perfect starting point for modeling your data types. Since we can always be more explicit, it is also possible to express types that are much more powerful: generalized algebraic datatypes (GADTs). Here is an example of a GADT where the type parameter (`T`) specifies the contents stored in the box: + +{% tabs types-adts-gadts-14 %} +{% tab 'Scala 3 only' %} ```scala enum Box[T](contents: T): case IntBox(n: Int) extends Box[Int](n) case BoolBox(b: Boolean) extends Box[Boolean](b) ``` +{% endtab %} +{% endtabs %} + Pattern matching on the particular constructor (`IntBox` or `BoolBox`) recovers the type information: + +{% tabs types-adts-gadts-15 %} +{% tab 'Scala 3 only' %} ```scala def extract[T](b: Box[T]): T = b match case IntBox(n) => n + 1 case BoolBox(b) => !b ``` -It is only safe to return an `Int` in the first case, since we know from pattern matching that the input was an `IntBox`. +{% endtab %} +{% endtabs %} +It is only safe to return an `Int` in the first case, since we know from pattern matching that the input was an `IntBox`. ## Desugaring Enumerations _Conceptually_, enums can be thought of as defining a sealed class together with its companion object. Let’s look at the desugaring of our `Color` enum above: + +{% tabs types-adts-gadts-16 class=tabs-scala-version %} +{% tab 'Scala 2' for=types-adts-gadts-16 %} +```scala +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) + } +} +``` +{% endtab %} + +{% tab 'Scala 3' for=types-adts-gadts-16 %} ```scala sealed abstract class Color(val rgb: Int) extends scala.reflect.Enum object Color: @@ -185,9 +276,11 @@ object Color: case 2 => Blue case _ => throw new NoSuchElementException(ordinal.toString) ``` +{% endtab %} +{% endtabs %} + Note that the above desugaring is simplified and we purposefully leave out [some details][desugar-enums]. While enums could be manually encoded using other constructs, using enumerations is more concise and also comes with a few additional utilities (such as the `fromOrdinal` method). - [desugar-enums]: {{ site.scala3ref }}/enums/desugarEnums.html diff --git a/_zh-cn/overviews/scala3-book/types-adts-gadts.md b/_zh-cn/overviews/scala3-book/types-adts-gadts.md index 9d3e469333..ce03cb767b 100644 --- a/_zh-cn/overviews/scala3-book/types-adts-gadts.md +++ b/_zh-cn/overviews/scala3-book/types-adts-gadts.md @@ -20,41 +20,59 @@ permalink: "/zh-cn/scala3/book/:title.html" _enumeration_ 用于定义由一组命名值组成的类型: +{% tabs types-adts-gadts-1 %} +{% tab 'Scala 3 only' %} ```scala enum Color: case Red, Green, Blue ``` +{% endtab %} +{% endtabs %} 这可以看作是以下的简写: +{% tabs types-adts-gadts-2 %} +{% tab 'Scala 3 only' %} ```scala enum Color: case Red extends Color case Green extends Color case Blue extends Color ``` +{% endtab %} +{% endtabs %} #### 参数 枚举可以参数化: +{% tabs types-adts-gadts-3 %} +{% tab 'Scala 3 only' %} ```scala enum Color(val rgb: Int): case Red extends Color(0xFF0000) case Green extends Color(0x00FF00) case Blue extends Color(0x0000FF) ``` +{% endtab %} +{% endtabs %} 这样,每个不同的变体都有一个值成员 `rgb`,它被分配了相应的值: +{% tabs types-adts-gadts-4 %} +{% tab 'Scala 3 only' %} ```scala println(Color.Green.rgb) // prints 65280 ``` +{% endtab %} +{% endtabs %} #### 自定义 枚举也可以有自定义: +{% tabs types-adts-gadts-5 %} +{% tab 'Scala 3 only' %} ```scala enum Planet(mass: Double, radius: Double): @@ -67,9 +85,13 @@ enum Planet(mass: Double, radius: Double): case Earth extends Planet(5.976e+24, 6.37814e6) // 5 or 6 more planets ... ``` +{% endtab %} +{% endtabs %} 像类和 `case` 类一样,你也可以为枚举定义一个伴生对象: +{% tabs types-adts-gadts-6 %} +{% tab 'Scala 3 only' %} ```scala object Planet: def main(args: Array[String]) = @@ -78,17 +100,23 @@ object Planet: for (p <- values) println(s"Your weight on $p is ${p.surfaceWeight(mass)}") ``` +{% endtab %} +{% endtabs %} ## 代数数据类型 (ADTs) `enum` 概念足够通用,既支持_代数数据类型_(ADT)和它的通用版本(GADT)。 本示例展示了如何将 `Option` 类型表示为 ADT: +{% tabs types-adts-gadts-7 %} +{% tab 'Scala 3 only' %} ```scala enum Option[+T]: case Some(x: T) case None ``` +{% endtab %} +{% endtabs %} 这个例子创建了一个带有协变类型参数 `T` 的 `Option` 枚举,它由两种情况组成, `Some` 和 `None`。 `Some` 是_参数化_的,它带有值参数 `x`;它是从 `Option` 继承的 `case` 类的简写。 @@ -96,14 +124,20 @@ enum Option[+T]: 前面示例中省略的 `extends` 子句也可以显式给出: +{% tabs types-adts-gadts-8 %} +{% tab 'Scala 3 only' %} ```scala enum Option[+T]: case Some(x: T) extends Option[T] case None extends Option[Nothing] ``` +{% endtab %} +{% endtabs %} 与普通的 `enum` 值一样,`enum` 的情况是在 `enum` 的伴生对象中定义的,因此它们被引用为 `Option.Some` 和 `Option.None`(除非定义是在导入时单独列出): +{% tabs types-adts-gadts-9 %} +{% tab 'Scala 3 only' %} ```scala scala> Option.Some("hello") val res1: t2.Option[String] = Some(hello) @@ -111,10 +145,14 @@ val res1: t2.Option[String] = Some(hello) scala> Option.None val res2: t2.Option[Nothing] = None ``` +{% endtab %} +{% endtabs %} 与其他枚举用途一样,ADT 可以定义更多的方法。 例如,这里又是一个 `Option`,它的伴生对象中有一个 `isDefined` 方法和一个 `Option(...)` 构造函数: +{% tabs types-adts-gadts-10 %} +{% tab 'Scala 3 only' %} ```scala enum Option[+T]: case Some(x: T) @@ -128,6 +166,8 @@ object Option: def apply[T >: Null](x: T): Option[T] = if (x == null) None else Some(x) ``` +{% endtab %} +{% endtabs %} 枚举和 ADT 共享相同的句法结构,因此它们可以 被简单地视为光谱的两端,把二者混搭是完全可能的。 @@ -135,6 +175,8 @@ object Option: `Color` 的实现,可以使用三个枚举值或使用 RGB 值的参数化情况: +{% tabs types-adts-gadts-11 %} +{% tab 'Scala 3 only' %} ```scala enum Color(val rgb: Int): case Red extends Color(0xFF0000) @@ -142,26 +184,36 @@ enum Color(val rgb: Int): case Blue extends Color(0x0000FF) case Mix(mix: Int) extends Color(mix) ``` +{% endtab %} +{% endtabs %} #### 递归枚举 到目前为止,我们定义的所有枚举都由值或样例类的不同变体组成。 枚举也可以是递归的,如下面的自然数编码示例所示: +{% tabs types-adts-gadts-12 %} +{% tab 'Scala 3 only' %} ```scala enum Nat: case Zero case Succ(n: Nat) ``` +{% endtab %} +{% endtabs %} 例如,值 `Succ(Succ(Zero))` 表示一元编码中的数字 `2`。 列表可以以非常相似的方式定义: +{% tabs types-adts-gadts-13 %} +{% tab 'Scala 3 only' %} ```scala enum List[+A]: case Nil case Cons(head: A, tail: List[A]) ``` +{% endtab %} +{% endtabs %} ## 广义代数数据类型 (GADT) @@ -170,19 +222,27 @@ enum List[+A]: 这是一个 GADT 示例,其中类型参数 (`T`) 指定存储在框中的内容: +{% tabs types-adts-gadts-14 %} +{% tab 'Scala 3 only' %} ```scala enum Box[T](contents: T): case IntBox(n: Int) extends Box[Int](n) case BoolBox(b: Boolean) extends Box[Boolean](b) ``` +{% endtab %} +{% endtabs %} 特定构造函数(`IntBox` 或 `BoolBox`)上的模式匹配可恢复类型信息: +{% tabs types-adts-gadts-15 %} +{% tab 'Scala 3 only' %} ```scala def extract[T](b: Box[T]): T = b match case IntBox(n) => n + 1 case BoolBox(b) => !b ``` +{% endtab %} +{% endtabs %} 只有在第一种情况下返回一个 `Int` 才是安全的,因为我们从 pattern 匹配输入是一个“IntBox”。 @@ -191,6 +251,27 @@ def extract[T](b: Box[T]): T = b match _从概念上讲_,枚举可以被认为是定义一个密封类及其伴生对象。 让我们看看上面的 `Color` 枚举的无语法糖版本: +{% tabs types-adts-gadts-16 class=tabs-scala-version %} +{% tab 'Scala 2' for=types-adts-gadts-16 %} +```scala +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) + } +} +``` +{% endtab %} + +{% tab 'Scala 3' for=types-adts-gadts-16 %} ```scala sealed abstract class Color(val rgb: Int) extends scala.reflect.Enum object Color: @@ -205,6 +286,8 @@ object Color: case 2 => Blue case _ => throw new NoSuchElementException(ordinal.toString) ``` +{% endtab %} +{% endtabs %} 请注意,上面的去除语法糖被简化了,我们故意省略了[一些细节][desugar-enums]。