了解 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]
我的两个问题是:
- 这种脱糖机制究竟是如何工作的?特别是为什么
Some
默认扩展 Option[T]
而 None
扩展 Option[Nothing]
?
- 这似乎是一种奇怪的定义方式
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
的子类型?
我确定这里遗漏了一些非常基本的东西。
脱糖是编译阶段的一部分,您需要了解的更重要的是 scala3 中的枚举替换了 scala 2 的 coproduct/sum 类型。
它本质上是一个标记的联合类型。
没有什么是底层类型,所以它扩展自一切。它是 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;
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
可以来自我程序中的任何地方,而我们没有决定我们希望它成为什么类型 up-front。我们不能(安全地)在可变数据类型上使用协方差,所以这一切都有效,因为 List
和 Option
是不可变的。像 ArrayBuffer
这样的可变 collection 在其类型参数方面是不变的。 (注意:Java 的 built-in 数组是协变的,那是 unsound 并导致各种问题)
几周后我将开始担任 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]
我的两个问题是:
- 这种脱糖机制究竟是如何工作的?特别是为什么
Some
默认扩展Option[T]
而None
扩展Option[Nothing]
? - 这似乎是一种奇怪的定义方式
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
的子类型?
我确定这里遗漏了一些非常基本的东西。
脱糖是编译阶段的一部分,您需要了解的更重要的是 scala3 中的枚举替换了 scala 2 的 coproduct/sum 类型。 它本质上是一个标记的联合类型。
没有什么是底层类型,所以它扩展自一切。它是 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
:
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;
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
可以来自我程序中的任何地方,而我们没有决定我们希望它成为什么类型 up-front。我们不能(安全地)在可变数据类型上使用协方差,所以这一切都有效,因为 List
和 Option
是不可变的。像 ArrayBuffer
这样的可变 collection 在其类型参数方面是不变的。 (注意:Java 的 built-in 数组是协变的,那是 unsound 并导致各种问题)