scala编译器对类型系统中的单位类型有什么特殊规定

What special rules does the scala compiler have for the unit type within the type system

Unit 在生成字节码时得到编译器的特殊处理,因为它类似于 jvm 上的 void。但从概念上讲,作为 Scala 类型系统中的一种类型,它似乎在语言本身中也得到了特殊对待(下面的示例)。

所以我的问题是澄清这一点并了解使用的机制以及是否真的对 Unit 类型进行了特殊处理。


示例 1:

对于像 Seq 这样的 "normal" scala 类型,如果一个方法 returns Seq,那么你必须 return Seq (或者扩展 Seq)

的更具体的类型
def foo1: Seq[Int] = List(1, 2, 3)
def foo2: Seq[Int] = Vector(1, 2, 3)
def foo3: Seq[Int] = "foo" // Fails

前两个示例可以编译,因为 List[Int]Vector[Int]Seq[Int] 的子类型。第三个失败了,因为 String 不是。

但是,如果我将第三个示例更改为 return Unit,它 编译并且 运行 没有问题,即使 String 不是 Unit 的子类型:

def foo3(): Unit = "foo" // Compiles (with a warning)

我不知道在 Scala 中允许此异常的任何其他类型。编译器在类型系统级别是否对 Unit 类型有特殊规则,或者是否有某种更通用的机制在起作用,例如隐式转换。


示例 2:

我也不清楚单位在通常应用差异规则的情况下如何相互作用。

例如,我们有时会遇到这个错误 Future[Unit] 我们不小心使用 map 而不是 flatMap 并创建 Future[Future]:

def save(customer: Customer): Future[Unit] = ... // Save to database

def foo: Future[Unit] = save(customer1).map(_ => save(customer2))

map 正在创建 Future[Future[Unit]],编译器需要 Future[Unit]。然而这编译!

起初我以为这是因为 Future[+T] 是协变的,但实际上 Future[Unit] 不是 Unit 的子类型所以它似乎不是那样。

例如,如果类型更改为 Boolean,编译器会检测到错误:

def save(customer: Customer): Future[Boolean] = ...

def foo: Future[Boolean] = save(customer1).map(_ => save(customer2)) // Compiler fails this

并且对于所有其他非 Unit 类型,它不会编译(Any 除外,因为 Future[Any] 碰巧是 Any 的子类型)。

那么编译器在这种情况下是否有特殊规则?或者是否有更一般的过程发生?

scala language specification 第 6.26.1 章所述:

Value Discarding

If e has some value type and the expected type is Unit, e is converted to the expected type by embedding it in the term { e; () }.

rethab 的回答已经为您提供了规范的 link;让我补充一下

  • 您可以通过 -Xfatal-warnings 编译器标志 禁用 这个(使警告成为错误)
  • 使用 -Ywarn-value-discard 标志,您会收到更好的 消息 ;对于 foo3,编译器警告将提供更多信息 discarded non-Unit value

请注意,此 "any to Unit" 转换是编译器的魔法,因此 -Yno-predef-Yno-imports 都不会禁用它;你确实需要上面的标志。我认为这是语言规范的一部分,这是一个错误,就好像出于某种原因你想要这种可疑的行为一样,你可以添加类似

的内容
implicit def any2Unit(a: Any): Unit = ()

虽然选择退出它需要一个不受支持的(根据定义,因为它 破坏 规范)编译器标志。

我还推荐 wartremover, where you have this 等等。

我将回答标题问题以获得更多报道。 Unit 在一些地方得到特殊对待,比那些代码示例中发生的事情更多。在某种程度上,这是因为 Unit 是编译器的虚构,在 JVM 上缩减为 void


值丢弃

这是最让人意外的案例。任何时候某个值的预期类型是 Unit,编译器根据 SLS - 6.26.1:

在生成该值的表达式末尾添加 Unit

If ee has some value type and the expected type is Unit, ee is converted to the expected type by embedding it in the term { ee; () }.

因此,

def foo3(): Unit = "foo"

变成:

def foo3(): Unit = { "foo" ; () }

同样,

def foo: Future[Unit] = save(customer1).map(_ => save(customer2))

变成:

def foo: Future[Unit] = save(customer1).map(_ => { save(customer2); () })

这样做的 好处 是如果您不想的话,您不需要让方法的最后一条语句具有 Unit 类型。然而,这个好处很小,因为如果你的方法的最后一个语句 returns Unit 不是 Unit,那么 通常 表示一个错误,这就是为什么它有一个警告标志 (-Ywarn-value-discard)。

一般来说,如果可能的话,我发现 return 更具体的类型比 returning Unit 更好。例如,当保存到数据库时,您可以 return 保存的值(可能使用新 ID 或其他内容)。


值Class

Unit 是由 Scala 编译器创建的一个值 class,只有一个实例(如果它根本不需要实例化为 class)。这意味着它在 JVM 上编译为原始 void,除非您将其视为 class(例如 ().toString)。它在规范中有自己的部分,SLS - 12.2.13


空块类型

SLS - 6.11开始,假定空块的默认类型为Unit。例如:

scala> val x = { }
x: Unit = ()

等于

当比较一个 Unit 和另一个 Unit (必须是相同的 object,因为只有一个),编译器会发出一个特殊的警告来通知你一些事情您的程序中可能有误。

scala> ().==(())
<console>:12: warning: comparing values of types Unit and Unit using `==' will always yield true
       ().==(())
            ^
res2: Boolean = true

铸造

您可以将任何内容强制转换为 Unit,因为编译器会将其优化掉(尽管我不清楚在类型推断后值丢弃是否接管)。

object Test {
  val a = "a".asInstanceOf[Unit]
  val b = a
}

变成:

object Test extends Object {
  def <init>(): Test.type = {
    Test.super.<init>();
    ()
  };
  private[this] val a: scala.runtime.BoxedUnit = scala.runtime.BoxedUnit.UNIT;
  <stable> <accessor> def a(): Unit = ();
  private[this] val b: scala.runtime.BoxedUnit = scala.runtime.BoxedUnit.UNIT;
  <stable> <accessor> def b(): Unit = ()
}