在类型系统中,scala编译器对单元类型有什么特殊的规则 [英] What special rules does the scala compiler have for the unit type within the type system

查看:98
本文介绍了在类型系统中,scala编译器对单元类型有什么特殊的规则的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

所以我的问题是关于澄清这个问题并了解使用了哪些机制以及是否对 Unit 类型有特殊待遇。






示例1



对于正常scala类型,如 Seq Seq ,那么你必须返回 Seq (或者一个更具体的类型, code> Seq )

  def foo1:Seq [Int] = List(1, 2,3)
def foo2:Seq [Int] = Vector(1,2,3)
def foo3:Seq [Int] =foo//失败
$ b

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



但是如果我改变第三个例子返回 Unit 尽管 String 不是 / em> Unit 的子类型:

  def foo3(): Unit =foo//编译(带有警告)

我不知道任何其他类型,这个例外将被允许在scala中。因此,编译器在类型系统级别对 Unit 类型有特殊的规则,或者在工作中有某种更通用的机制,隐式转换。




示例2:



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

例如,我们有时会使用 Future [单元] 我们意外地使用 map 而不是 flatMap 和创建 Future [Future]

  def save(customer:Customer ):Future [Unit] = ... //保存到数据库

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 failed this

非 - Unit 类型将不会编译(除任何因为未来[Any] 碰巧是巧合的任何的子类型)。

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

解决方案

我将回答题目问题以获得更多报道。 Unit 在几个地方得到了特殊待遇,比这些代码示例中的更多。部分原因是这是因为 Unit 是编译器的一个参数,它在JVM上简化为 void






取消价值

这是对人们来说最令人惊讶的情况。任何时候某个值的预期类型都是 Unit ,编译器会在生成的表达式结尾处贴上 Unit 该值根据 SLS - 6.26。 1


如果 ee 有一些值类型,类型为单元 ee 通过将其嵌入术语 { EE; ()}


因此,

 def foo3():Unit =foo

变成:

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

同样,

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


$ b 成为:

  def foo:Future [单位] =保存(客户1)。 map(_ => {save(customer2);()})

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



一般而言,如果可能的话,我发现最好返回一个更具体的类型,而不是返回 Unit 。例如,当保存到数据库时,您可能会返回保存的值(可能带有新的ID或其他内容)。






值类
$ b

单元是由Scala编译器,只有一个实例(如果它需要实例化为一个类)。这意味着它可以编译到JVM上的基元 void ,除非您将它视为一个类(例如()。toString )。它在规范中有它自己的部分, SLS - 12.2.13






空块类型



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

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






等于



比较单位与另一个 Unit (它必须是同一个对象,因为只有一个),编译器会发出一个特殊的警告,告诉你程序中可能有错误。

  scala> ()。==(())
< console>:12:warning:使用'=='比较类型Unit和Unit的值总是会产生真实的
()。==(() )
$
res2:Boolean = true






投射



您可以将任何内容投射到 Unit ,as编译器会优化它(尽管我不清楚在类型推断后值丢弃是否被接管)。

  object Test {
val a =a.asInstanceOf [单位]
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> <存取> def a():Unit =();
private [this] val b:scala.runtime.BoxedUnit = scala.runtime.BoxedUnit.UNIT;
< stable> <存取> def b():Unit =()
}


The Unit gets special handling by the compiler when generating byte code because it's analogous to void on the jvm. But conceptually as a type within the scala type system, it seems like it also gets special treatment in the language itself (examples below).

So my question is about clarifying this and understanding what mechanisms are used and if there really is special treatment for the Unit type.


Example 1:

For "normal" scala types like Seq, if a method returns Seq, then you must return Seq (or a more specific type that extends Seq)

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

The first two examples compile because List[Int] and Vector[Int] are subtypes of Seq[Int]. The third one fails because String isn't.

But if I change the third example to return Unit though, it will compile and run without issue even though String isn't a subtype of Unit:

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

I don't know of any other type which this exception would be allowed for in scala. So does the compiler have special rules for the Unit type at the type system level, or is there some kind of more general mechanism at work e.g. an implicit conversion.


Example 2:

I'm also not clear how unit interacts in situations where variance rules would normally be applied.

For example, we sometimes hit this bug with Future[Unit] where we accidentally use map instead of flatMap and create a Future[Future]:

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

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

The map is creating a Future[Future[Unit]] and the compiler requires a Future[Unit]. Yet this compiles!

At first I thought this was because Future[+T] is covariant, but actually Future[Unit] isn't a subtype of Unit so it doesn't seem to be that.

If the type gets changed to Boolean for example, the compiler detects the bug:

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

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

And for every other non-Unit type it won't compile (except Any because Future[Any] happens to be a subtype of Any by coincidence).

So does the compiler have special rules in this case? Or is there a more general process happening?

解决方案

I'm going to answer the title question for more coverage. Unit gets special treatment in a few places, more than what's going on in those code examples. In part, this is because Unit is a figment of the compiler that reduces to void on the JVM.


Value Discarding

This is the most surprising case for people. Any time the expected type of some value is Unit, the compiler tacks on Unit at the end of the expression that produces the value, according to the SLS - 6.26.1:

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; () }.

Thus,

def foo3(): Unit = "foo"

becomes:

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

Likewise,

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

becomes:

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

The benefit of this is that you don't need to have the last statement of a method have the type Unit if you don't want to. This benefit is small however, because if the last statement of your method that returns Unit isn't a Unit, that usually indicates an error, which is why there is a warning flag for it (-Ywarn-value-discard).

In general, I find it better to return a more specific type, if possible, rather than returning Unit. For example, when saving to a database, you may be able to return the saved value (perhaps with a new ID, or something).


Value Class

Unit is a value class created by the Scala compiler, with only one instance (if it needs to be instantiated as a class at all). This means that it compiles down to the primitive void on the JVM, unless you treat it as a class (e.g. ().toString). It has its very own section in the specification, SLS - 12.2.13.


Empty Block Type

From the SLS - 6.11, the default type of an empty block is assumed to be Unit. For example:

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


Equals

When comparing a Unit to another Unit (which must be the same object, since there is only one), the compiler will emit a special warning to inform you something is likely wrong in your program.

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


Casting

You can cast anything to a Unit, as the compiler will optimize it away (though it's unclear to me if value discarding takes over after type inference).

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

becomes:

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

这篇关于在类型系统中,scala编译器对单元类型有什么特殊的规则的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆