【问题标题】:What special rules does the scala compiler have for the unit type within the type systemscala编译器对类型系统中的单元类型有什么特殊规则
【发布时间】:2016-12-20 09:05:39
【问题描述】:

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

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


示例 1:

对于像Seq这样的“普通”scala类型,如果一个方法返回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

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

但是,如果我将第三个示例更改为返回 Unit,它编译并运行而不会出现问题,即使 String 不是 @ 的子类型987654335@:

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 的子类型)。

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

【问题讨论】:

  • 基本上,如果您将某种类型声明为 Unit(例如,值或函数返回),编译器会将其具有的任何值转换为 Unit。不确定这是否是您正在寻找的答案,或者您是否正在寻找更多内部细节..

标签: scala functional-programming static-typing


【解决方案1】:

scala language specification 第 6.26.1 章所述:

价值丢弃

如果 e 有一些值类型并且预期的类型是 Unit,则 e 被转换 通过将其嵌入术语 { e; 到预期的类型; () }。

【讨论】:

    【解决方案2】:

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

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

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

    implicit def any2Unit(a: Any): Unit = ()
    

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

    我还推荐wartremover,你有this 等等。

    【讨论】:

      【解决方案3】:

      我将回答标题问题以获得更多报道。 Unit 在一些地方得到了特殊对待,比那些代码示例中发生的更多。部分原因是,Unit 是编译器的虚构,在 JVM 上简化为 void


      价值丢弃

      这是最让人们惊讶的案例。根据SLS - 6.26.1,只要某个值的预期类型为Unit,编译器就会在产生该值的表达式末尾添加Unit

      如果ee 具有某个值类型并且预期类型为Unit,则ee 通过嵌入术语{ 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 类型。然而,这个好处很小,因为如果返回 Unit 的方法的最后一条语句不是 Unit,那么 通常 表示错误,这就是为什么会有警告标志的原因(-Ywarn-value-discard)。

      一般来说,如果可能的话,我发现返回一个更具体的类型比返回Unit 更好。例如,当保存到数据库时,您可能能够返回保存的值(可能带有新的 ID 或其他东西)。


      值类

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


      空块类型

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

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

      等于

      当比较 Unit 和另一个 Unit(必须是同一个对象,因为只有一个对象)时,编译器会发出一个特殊警告,通知您程序中可能有问题。

      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 = ()
      }
      

      【讨论】:

      • 感谢您的精彩回答!我认为当您链接语言规范时,您的意思是 6.26.1,而不是 6.22.1。
      猜你喜欢
      • 2017-02-25
      • 1970-01-01
      • 1970-01-01
      • 2020-06-30
      • 1970-01-01
      • 2019-08-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多