问题描述
几周后我将开始担任 Scala 角色,但我之前没有写过任何 Scala(是的,我未来的雇主知道这一点),尽管我已经写了很多 C# 和 Haskell。无论如何,我正在浏览 Scala 3 书,并找到了这本example:
enum Option[+T]:
case Some(x: T)
case None
这显然是dusugars:
enum Option[+T]:
case Some(x: T) extends Option[T]
case None extends Option[nothing]
我的两个问题是:
enum Option[+T]:
case Some(x: T) extends Option[T]
case None extends Option[T]
确实使用 None
扩展 Option[None]
这不会失败吗?
Option[string] x = None;
因为 Option[T]
在 T
中是协变的,而 None
不是 string
的子类型吗?
我确定我在这里遗漏了一些非常基本的东西。
解决方法
Issue #1970 上的原始提案是关于枚举脱糖工作原理的一个很好的来源。
该页面上与您的问题相关的部分标题为“脱糖”。让我们来看看您的 Option
定义。
enum Option[+T]:
case Some(x: T)
case None
Some
是具有类型参数的枚举下的参数化情况,因此适用规则 #4
如果 E 是一个带有类型参数 Ts 的枚举类,那么它的伴生对象中有一个 case,没有 extends 子句
case C <params> <body>
...
对于C没有类型参数的情况,假设E的类型参数是
V1 T1 > L1 <: U1,...,Vn Tn >: Ln <: Un (n > 0)
其中每个方差 Vi 是“+”或“-”。然后案例扩展到
case C <params> extends E[B1,Bn] <body>
因此,您的 Some
案例获得了 extends
的 Option[T]
子句,正如您所期望的那样。然后规则 #5 为我们提供了我们的实际案例类。
另一方面,出于同样的原因,您的 None
使用没有类型参数,因此结果基于差异。
- 如果
T
是不变的,我们必须明确指定一个extends
子句 - 如果
T
是协变的,我们得到Nothing
- 如果
T
是逆变的,我们得到Any
您的 T
是协变的,因此我们扩展了 Option[Nothing]
,记住它是 any Option[S]
的 S
子类型。因此,您的赋值(请记住,在 Scala 中,我们使用 val
/ var
来声明变量,而不仅仅是类型名称)
val x: Option[String] = None;
None
是 None.type
类型的值,它扩展了 Option[Nothing]
。 Option[Nothing]
是 Option[String]
的子类型,因为 Nothing
是 String
的子类型。这样赋值就成功了。
这与 Nil
(空列表)扩展 List[Nothing]
的原因相同。我可以构造一个具有具体类型(Nil
的子类型)的 List[Nothing]
,然后我可以出现并添加我想要的任何内容。结果列表不再是 List[Nothing]
; 1 +: Nil
是 List[Int]
而 "a" +: Nil
是 List[String]
,但重点是,Nil
可以来自我程序中的任何地方,而我们没有决定我们希望它是什么类型的前期。我们不能(安全地)在可变数据类型上使用协方差,所以这一切都有效,因为 List
和 Option
是不可变的。像 ArrayBuffer
这样的可变集合的类型参数是不变的。 (注意:Java 的内置数组是协变的,这就是 unsound 并会导致各种问题)
这种脱糖机制究竟是如何工作的?特别是 为什么 Some 默认扩展 Option[T] 而 None 扩展 选项[无]?
Some
默认扩展 Option[T]
因为它有一个类型参数作为参数 (x: T)
并且被类似地(但不完全相同)作为一个案例类,而 {{1 }} 被视为“常规”枚举值,因为它没有参数列表定义。
我们可以通过添加None
来进一步分析打字机编译阶段:
-Xprint:typer
我们可以看到 sealed abstract class MyOption[T >: Nothing <: Any]()
extends
Object(),scala.reflect.Enum {
+T
import MyOption.{MySome,None}
}
final lazy module val MyOption: MyOption$ = new MyOption$()
final module class MyOption$() extends AnyRef() { this: MyOption.type =>
final case class MySome[T](x: T) extends MyOption[MySome.this.T]() {
+T
val x: T
def copy[T](x: T): MyOption.MySome[T] = new MyOption.MySome[T](x)
def copy$default$1[T]: T = MySome.this.x
def ordinal: Int = 0
def _1: T = this.x
}
final lazy module val MySome: MyOption.MySome$ = new MyOption.MySome$()
final module class MySome$() extends AnyRef() {
this: MyOption.MySome.type =>
def apply[T](x: T): MyOption.MySome[T] = new MyOption.MySome[T](x)
def unapply[T](x$1: MyOption.MySome[T]): MyOption.MySome[T] = x$1
override def toString: String = "MySome"
}
case <static> val None: MyOption[Nothing] =
{
final class $anon() extends MyOption[Nothing](),scala.runtime.EnumValue {
def ordinal: Int = 1
}
new $anon(): MyOption[Nothing] & runtime.EnumValue
}
扩展为 MySome
,而 case class
扩展为 None
上定义的值。
Silvio provided the "specification" 用于 MyOption
定义(截至今天 (08/06/2021) 我找不到正式的定义),其中规则 4 和规则 7 是我们正在寻找的:
如果 E 是一个带有类型参数 Ts 的枚举类,那么它的伴生对象中有一个 case,没有 extends 子句
enum
...
对于C没有类型参数的情况,假设E的类型参数是
case C <params> <body>
其中每个方差 Vi 是“+”或“-”。然后案例扩展到
V1 T1 > L1 <: U1,Vn Tn >: Ln <: Un (n > 0)
对于 case C <params> extends E[B1,Bn] <body>
情况:
不带类型参数的枚举类 E 的 case C 展开
None
这里,$new 是一个私有方法,它创建一个 E 的实例(见下文)。
这似乎是一种定义 Option 的奇怪方式。
不是真的,仔细想想还是有道理的。在这种情况下,val C = $new(n,"C")
被视为单例,并且由于 None
的参数类型是协变的,而 Nothing
is a bottom type which means it inherits all other types in Scala,它适用于任何赋值。
-
脱糖是编译器阶段的一部分,您需要了解的更重要的是 scala3 中的 enum 替换了 scala 2 的 coproduct/sum 类型。 它本质上是一个标记联合类型。
-
Nothing 是底部类型,因此它扩展了所有内容。它是 Any(root object) 的对偶
ps:如果你愿意,我可以扩展那些,让我知道