了解 Scala 3 书中 "Option" 的定义和脱糖

Understanding definition and desugaring of "Option" in Scala 3 book

几周后我将开始担任 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]

我的两个问题是:

  1. 这种脱糖机制究竟是如何工作的?特别是为什么 Some 默认扩展 Option[T]None 扩展 Option[Nothing]?
  2. 这似乎是一种奇怪的定义方式 Option。我会这样定义它:
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 的子类型?

我确定这里遗漏了一些非常基本的东西。

  1. 脱糖是编译阶段的一部分,您需要了解的更重要的是 scala3 中的枚举替换了 scala 2 的 coproduct/sum 类型。 它本质上是一个标记的联合类型。

  2. 没有什么是底层类型,所以它扩展自一切。它是 Any(root object)

    的对偶

ps:如果你愿意,我可以展开这些,让我知道

How exactly does the mechanism of this desugaring work? In particular why does Some by default extend Option[T] whereas None extends Option[Nothing]?

Some 默认扩展 Option[T] 因为它有一个类型参数作为参数 (x: T) 并且与 case [=48= 类似(但不完全相同) ],而 None 被视为“常规”枚举值,因为它没有参数列表定义。

我们可以通过添加 -Xprint:typer:

来进一步分析 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[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: MyOption.MySome[T]): MyOption.MySome[T] = x
  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被扩展为定义在MyOption上的值。

用于 enum 定义(截至今天 (08/06/2021) 我找不到正式的定义),其中规则 4 和 7 是我们正在寻找的:

If E is an enum class with type parameters Ts, then a case in its companion object without an extends clause

case C <params> <body>

...

For the case where C does not have type parameters, assume E's type parameters are

V1 T1 > L1 <: U1 ,   ... ,    Vn Tn >: Ln <: Un      (n > 0)

where each of the variances Vi is either '+' or '-'. Then the case expands to

case C <params> extends E[B1, ..., Bn] <body>

对于 None 案例:

case C of an enum class E that does not take type parameters expands to

val C = $new(n, "C")

Here, $new is a private method that creates an instance of of E (see below).


This seems like a strange way to define Option.

其实也不是,仔细想想也有道理。在这种情况下,None 被视为单例,并且由于 T 在其参数类型上是协变的,而 Nothing is a bottom type which means it inherits all other types in Scala,它适用于任何赋值。

Issue #1970 上的原始提案是枚举脱糖工作原理的一个很好的来源。

该页面上与您的问题相关的部分标题为“脱糖”。我们来看看您的 Option 定义。

enum Option[+T]:
  case Some(x: T)
  case None

Some 是具有类型参数的枚举下的参数化情况,因此适用规则 #4

If E is an enum class with type parameters Ts, then a case in its companion object without an extends clause

case C <params> <body>

...

For the case where C does not have type parameters, assume E's type parameters are

V1 T1 > L1 <: U1 ,   ... ,    Vn Tn >: Ln <: Un      (n > 0)

where each of the variances Vi is either '+' or '-'. Then the case expands to

case C <params> extends E[B1, ..., Bn] <body>

所以您的 Some 案例得到 Option[T]extends 子句,如您所料。然后规则 #5 得到我们的实际案例 class.

另一方面,你的None,同理,使用类型参数,所以结果是基于方差的。

  • 如果 T 不变,我们 必须 明确指定 extends 子句
  • 如果 T 是协变的,我们得到 Nothing
  • 如果 T 是逆变的,我们得到 Any

你的 T 是协变的,所以我们扩展 Option[Nothing],记住它是 Option[S] 的子类型,用于 any S.因此,您的赋值(请记住,在 Scala 中,我们使用 val / var 来声明变量,而不仅仅是类型名称)

val x: Option[String] = None;

NoneNone.type 类型的值,它扩展了 Option[Nothing]Option[Nothing]Option[String] 的子类型,因为 NothingString 的子类型。所以赋值成功。

这与 Nil(空列表)扩展 List[Nothing] 的原因相同。我可以构造一个 Nil ,它有一个具体类型(List[Nothing] 的一个子类型),然后我可以在前面加上我想要的任何东西。结果列表不再是 List[Nothing]1 +: NilList[Int]"a" +: NilList[String],但重点是,Nil 可以来自我程序中的任何地方,而我们没有决定我们希望它成为什么类型 up-front。我们不能(安全地)在可变数据类型上使用协方差,所以这一切都有效,因为 ListOption 是不可变的。像 ArrayBuffer 这样的可变 collection 在其类型参数方面是不变的。 (注意:Java 的 built-in 数组是协变的,那是 unsound 并导致各种问题)