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

rmi*_*min 22 functional-programming scala static-typing

Unit编译器生成的字节码的时候,因为它是类似于得到特殊的处理void在JVM上.但从概念上讲,作为scala类型系统中的一种类型,它似乎也在语言本身得到特殊处理(下面的例子).

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


例1:

对于"普通"scala类型Seq,如果方法返回Seq,则必须返回Seq(或更具体的类型Seq)

def foo1: Seq[Int] = List(1, 2, 3)
def foo2: Seq[Int] = Vector(1, 2, 3)
def foo3: Seq[Int] = "foo" // Fails
Run Code Online (Sandbox Code Playgroud)

前两个例子编译,因为List[Int]Vector[Int]是的亚型Seq[Int].第三个失败,因为String不是.

但是,如果我改变了第三个例子返回Unit的是,它编译并没有问题,运行,即便String 是没有的子类型Unit:

def foo3(): Unit = "foo" // Compiles (with a warning)
Run Code Online (Sandbox Code Playgroud)

我不知道在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))
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

对于其他所有非Unit类型,它将无法编译(除非Any因为Future[Any]碰巧是Any巧合的子类型).

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

ret*_*hab 15

scala语言规范第6.26.1章中所述:

价值丢弃

如果e具有某种值类型且期望的类型是Unit,则通过将e嵌入到术语{e;中来将e转换为期望的类型.()}.


Edu*_*bes 13

rethab的回答已经给你链接到规范; 让我补充一点

  • 您可以通过编译器标志禁用此功能(使警告成为错误)-Xfatal-warnings
  • 你会用旗帜得到更好的信息-Ywarn-value-discard ; 对于foo3编译器警告将提供更多信息discarded non-Unit value

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

implicit def any2Unit(a: Any): Unit = ()
Run Code Online (Sandbox Code Playgroud)

选择退出时需要不支持(根据定义,因为它违反了规范)编译器标志.

我也推荐wartremover,你有这个以及更多.


Mic*_*jac 12

我将回答标题问题以获得更多报道.Unit在一些地方得到特殊待遇,而不是那些代码示例中发生的事情.在某种程度上,这是因为Unit编译器的图形减少到void了JVM.


价值丢弃

这是人们最令人惊讶的情况.根据SLS-6.26.1,只要某个值的预期类型是Unit,编译器就会Unit在产生该值的表达式的末尾处理:

如果ee具有某种值类型且期望的类型是Unit,ee则通过将其嵌入到术语中而将其转换为期望的类型{ ee; () }.

从而,

def foo3(): Unit = "foo"
Run Code Online (Sandbox Code Playgroud)

变为:

def foo3(): Unit = { "foo" ; () }
Run Code Online (Sandbox Code Playgroud)

同样,

def foo: Future[Unit] = save(customer1).map(_ => save(customer2))
Run Code Online (Sandbox Code Playgroud)

变为:

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

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

一般来说,如果可能的话,我发现更好地返回更具体的类型,而不是返回Unit.例如,在保存到数据库时,您可能能够返回保存的值(可能使用新ID或其他内容).


价值等级

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


空块类型

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

scala> val x = { }
x: Unit = ()
Run Code Online (Sandbox Code Playgroud)

等于

将a Unit与另一个Unit(必须是同一个对象,因为只有一个)进行比较时,编译器会发出一个特殊警告,通知你程序中可能存在错误.

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

铸件

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

object Test {
  val a = "a".asInstanceOf[Unit]
  val b = a
}
Run Code Online (Sandbox Code Playgroud)

变为:

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 = ()
}
Run Code Online (Sandbox Code Playgroud)